Book Review: Think Python 2e

Intro

This a review of Think Python 2e by Allen Downey.

This book is divided into 21 chapters and is about 257 pages long.

1 – Way of the Program 🥋

  • The most important skill for a computer scientist is problem solving.
  • A program is a sequence of instructions that specifies how to perform a computation.
  • Formal languages are designed by people for specific applications.
  • Evidence shows people respond to computers as if they are people (the media equation, Reeves & Nass 1996).

2 – Variables, Expressions, and Statements ⚽

  • A variable is a name that refers to a value.
  • An assignment creates new variables and gives them values, e.g., x = 2.
  • An expression is a combination of values, variables, and operators, e.g., x + y.
  • A statement is a unit of code that the Python interpreter can execute, e.g., print('hello world').

3 – Functions

  • The expression in parentheses is called the argument of the function.
  • A module is a file that contains a collection of related functions.
  • A function definition specifies the name of a new function and the sequence of statements that execute when the function is called.
  • To ensure that a function is defined before its first use, you have to know the order statements run, which is called the flow of execution.
  • A parameter is what is defined in the function, and the argument is what is passed into the function.
  • To keep track of which variables can be used, we can draw stack diagrams.
    • Each function is represented by a frame.
    • If an error occurs, a list of functions is printed, which is called a traceback.
  • Fruitful 🍇 functions return something; void 🌌 functions don’t.

4 – Interface Design

  • Turtle 🐢 module.
  • Wrapping a piece of code in a function is called encapsulation 💊.
  • A docstring 📄 is a string at the beginning of a function that explains the interface, like:
def x:
    """this function does something"""




5 – Conditionals and Recursion 🔁

  • Floor division operator // divides two numbers and rounds down to an integer; an alternative is the modulus operator %, which divides two numbers and returns the remainder.
  • Three logical operators: and, or, and not.
  • The bottom of a recursive function stack is called the base case as there are no more frames to call.

6 – Fruitful Functions 🍓

  • Code that’s never reached is called dead code 😵.
  • To deal with increasingly complex programs, you can try incremental development. This is when you add and test a small amount of code each time.
  • Scaffolding 🚧 is when you have code that is helpful for building the program but not part of the final product.
  • Following the flow of execution is one way to read a program; the other way is to do the ‘leap of faith’, which is when you assume the function works correctly.

7 – Iteration 🔂

  • Reassignment is when you make an existing variable refer to a new value; equality is a symmetric relationship, and assignment is not.
>>> x = 5
>>> x
5
>>> x = 7
>>> x
7
  • An algorithm is a mechanical process for solving a category of problems.
  • Exercise 7.3 – Ramanujan’s infinite series:
import math

def estimate_pi():
    sum = 0
    epsilon = math.pow(10, -15)
    k = 0
    i = 1  # initialize i, the value doesn't matter
    while i > epsilon:
        i = math.factorial(4 * k) * (1103 + 26390 * k) \
            / (math.pow(math.factorial(k), 4) * math.pow(396, 4 * k))
        sum += i
        k += 1
    inverse = 2 * math.sqrt(2) * sum / 9801
    return 1 / inverse

print(estimate_pi())
print(math.pi)
print(estimate_pi() - math.pi)

8 – Strings 🧵

  • A string is a sequence of characters.
  • Traversal is when you visit and perform computation on each character.
  • A segment of a string is called a slice.
  • Python 🐍 strings are immutable.
  • A method call is called an invocation.

9 – Word Play

  • If you see that uses_all was an instance of a previously solved problem, you are practicing a program development plan called reduction to a previously solved problem.

10 – Lists

  • A list is a sequence of values; you can have nested lists.
  • Lists are mutable.
  • You can traverse a list with a for loop.
def add_all(t):
    total = 0
    for x in t:
        total += x
    return total
  • As the loop runs, total accumulates the sum of elements. A variable used like this is sometimes called an accumulator.
>>> t = [1, 2, 3]
>>> sum(t)
6
  • An operation like above combines a sequence of elements into a single value and is sometimes called reduce.
  • An operation like capitalize_all is sometimes called a map as it ‘maps’ a function onto each element in a list.
  • You can use pop, del, or remove to delete elements from a list.
  • The association of a variable within an object is called a reference.
  • An object with more than one reference has more than one name, and we can say that the object is aliased.

11 – Dictionaries 📚

  • A dictionary is like a list but more general. A dictionary represents a mapping from keys to values.
  • Hashtable explanation on page 251.
  • Given a dictionary d and a key k, we can find a corresponding value v = d[k]; this operation is called a lookup.
  • raise statement causes an exception.
  • A singleton is a list that contains a single element.
  • Dictionary is implemented using a hashtable, which means keys must be hashable (e.g., lists cannot be keys); keys must be immutable to maintain idempotent lookups.
  • Recursive Fibonacci algorithm has a call graph that shows repeated computed values.
  • A previously computed value that’s stored for later use is called a memo.
# memoized version of fibonacci

known = {0:0, 1:1}

def fibonacci(n):
    if n in known:
        return known[n]

    res = fibonacci(n-1) + fibonacci(n-2)
    known[n] = res
    return res
  • Variables in __main__ are sometimes called global as they can be accessed from any function.
  • It’s common to use global variables as flags.

12 – Tuples 👨‍👨‍👧‍👧

  • A tuple is a sequence of values; tuples are immutable.
>>> t = 'a', 'b', 'c'
>>> t
('a', 'b', 'c')
  • To create a tuple with a single element, you have to include a final comma.
>>> u = 'a',
>>> u
('a',)
  • Tuple assignment – no need for a third variable when swapping the values of two variables.
>>> a, b = b, a
  • divmod takes two arguments and returns a tuple of two values: the quotient and remainder.
>>> t = divmod(7, 3)
>>> t
(2, 1)
  • A parameter name that begins with * gathers arguments into a tuple.
def printall(*args):
    print(args)
  • The complement of gather is scatter (use * operator).
>>> t = (7,3)
>>> divmod(*t)  # scatter the tuple
  • zip function takes two or more sequences and returns a list of tuples where each tuple contains one element from each sequence.
  • Dictionaries 📚 have a method called items that returns a sequence of tuples, where each tuple is a key-value pair.
  • Compound data structures are useful but prone to ‘shape errors’.

13 – Data Structure Selection

  • Prefixes: strings, list of strings, tuple of strings.
  • Suffixes: list / histogram (dictionary).
  • Mapping from prefix to suffix: dictionary.

14 – Files 📂

  • Persistence is when some data is kept in permanent storage.
  • os module has functions for working with files and directories, e.g., os.cwd gets the name of the current directory.
  • Sometimes errors happen when reading/writing files; handling an exception with a try statement is called catching an exception.
  • A database is a file that’s organized for storing data; Python has a module called dbm.
  • pickle module can translate any type of object into a string suitable for storage in a database.

15 – Classes and Objects

  • OOP uses programmer-defined types to organize both code and data.
  • A programmer-defined type is also called a class.
  • A state diagram that shows an object and its attributes is called an object diagram.
  • An object that’s an attribute of another object is embedded.
  • Shallow copy copies the object and any references it contains, but not embedded objects.

16 – Classes and Functions

  • Prototype and patch is a type of development plan that deals with complex problems by starting with a simple prototype and increasingly dealing with complications.
  • Pure functions don’t have side effects.
  • Functions that modify objects they get as parameters can be called modifiers.
  • The author recommends you resort to writing pure functions whenever possible and use modifiers only if there’s a compelling advantage. This approach is called the functional programming style.
  • An alternative style to prototype and patch is designed development, where high-level insight into the problem can make programming easier.
  • Invariants are conditions that should always be true.

17 – Classes and Methods

  • A method is a function that’s associated with a particular class.
    • There are two syntactic differences: 1) they’re defined inside a class definition to make the relationship between the class and method explicit.
    • The syntax for invoking a method is different.
  • Positional argument is an argument that doesn’t have a default value.
sketch(parrot, cage, dead=True)
  • parrot and cage are positional, dead is a keyword argument.
  • __init__ is a special method that’s invoked when an object is instantiated.
  • __str__ is a special method that’s supposed to return a string representation of an object.
  • Changing the behavior of an operator so that it works with programmer-defined types is called operator overloading.
  • A type-based dispatch dispatches a computation to different methods based on the type of arguments.
  • Functions that work with several types are called polymorphic. Polymorphism 🐑 can facilitate code reuse.
  • A design principle that helps make software more maintainable is to keep the interface separate from the implementation.

18 – Inheritance

  • Inheritance is the ability to define a new class that’s a modified version of an existing class.
  • A veneer is a thin method that uses another method without doing much work.
  • When a new class inherits from an existing one, the existing one is called the parent and the new class is called the child.
  • Pros of inheritance include code reuse; cons of inheritance are that it sometimes makes programs difficult to read.
  • When objects in one class contain references to objects in another class, this relationship is called HAS-A.
  • When one class might inherit from another, this relationship is called IS-A.
  • These relationships can be shown in a class diagram.
  • Data encapsulation (aka data hiding) is the mechanism where implementation details of a class are hidden from a user.

19 – The Goodies

  • Conditional expressions:
y = math.log(x) if x > 0 else float('nan')
  • List comprehensions:
# without LC
def capitalize_all(t):
    res = []
    for s in t:
        res.append(s.capitalize())
    return res

# with LC
def capitalize_all(t):
    return [s.capitalize() for s in t]
  • Generator expressions are similar to LCs but with parentheses instead of [].
>>> g = (x**2 for x in range(5))
  • Knows how to iterate through a sequence of values but waits to be asked to do so.
  • any – takes a sequence of Booleans and returns True if any value is True.
>>> any([False, False, True])
True
  • all – returns True if every element of a sequence is True.
  • A set behaves like a collection of dictionary keys with no values.
def has_duplicates(t):
    return len(set(t)) < len(t)
  • A Counter is a natural way to represent a multiset (mathematical idea).
from collections import Counter

def is_anagram(w1, w2):
    return Counter(w1) == Counter(w2)
  • defaultdict is like a dictionary, but if you’re trying to access a key that doesn’t exist, it will create one on the fly.
  • A function used to create objects is sometimes called a factory.

20 – Debugging

  • Infinite recursion can cause a ‘Maximum recursion depth exceeded’ error.
  • If you’re stuck on a bug, maybe go for a walk.

21 – Algorithm Analysis

  • Analysis of algorithms is a branch of CS that studies the performance of algorithms, especially their runtime and space requirements.
  • Relative performance might depend on details of the dataset; one way to avoid this is to analyze the worst-case scenario.
  • The leading term is the term with the highest exponent.
  • In general, we expect an algorithm with a smaller leading term to be a better algorithm for large problems, but for small problems, there may be a crossover point.
  • An order of growth is a set of functions whose growth behavior is considered equivalent.
    • Some examples include O(1) constant, O(logn) (logarithmic for any base), O(n) linear, O(n log n) linearithmic, O(n2) quadratic, O(n3) cubic, O(cn) exponential (for any c).
  • In Python 🐍, most arithmetic operations are constant time. Indexing operations are also constant time.
  • A for loop that traverses a sequence/dictionary is usually linear.
  • Python dictionaries are implemented using hashtables.

Conclusion

Overall, I liked this book and recommend it to anyone who is interested in the Python programming language. It is written in an easy-to-read style, and the examples are quite manageable.

Additional reading

You can read Think Python online for free 🥰

Leave a Reply

Your email address will not be published. Required fields are marked *