Fluent Python

My notes when reading Luciano Ramalho's Fluent Python.

The Python Data Model

By implementing special methods (A.K.A. magic methods, like __len__, __getitem__, __repr__ etc.. Often pronounced dunder xx), your objects can utilize built-in functions and syntax like len(), [], for ... in ... and thus be considered Pythonic.

Understanding the Pythonic len(xx) over xx.len(): Think of these functions as unary operators.

An Array of Sequences

  • List Comprehension vs Generator Expression

    • List Comprehension: list_a = [i + j for i in ... for j in ...]

    This is a cartesian products example. for i in ... part is the outer loop.

    • Generator Expression: xx(i + j for i in ... for j in ...)

    The syntactic difference is () vs []. But under the hood it saves space by yielding item one by one so a full list is never constructed. Also it can be used to build many other containers.

  • Tuples

    • Tuples (Iterable) Unpacking

      • Use case: parallel assignment (can be nested), swap, % string formatting print('%s %s' % tup), passing function parameter f(*tup).

      • Works for any iterable as long as the iterable yields exactly one item per variable in the receiving end. The only exception is using * dicussed below.

      • a, *b, c = range(5) and b is [1, 2, 3]. Only one * prefix variable is allowed.

    • Named Tuples collections.namedtuple

      • Construction: Passing construct parameters by name or position. NamedTup._make(iterable). NamedTup(*iterable).

      • Accessing field by name or position.

      • ._asdict() return a collections.OrderedDict

    • Methods and attributes as an "immutable list": No appending/poping/inserting nor any inplace ops.

  • Slicing

    • [:3] exclude the last item.

    • Slice object.

      s = slice(begin, end, stride)
      line[s]
    • Under the hood:

      # v[a]
      v.__getitem__(a)
      # v[a, b]. Multidimensional. Used in Numpy.
      v.__getitem__((a, b))
    • Ellipsis object: function parameters f(a, ..., z) and slice a[i:...]. If a is four dimentional, this is a shortcut for a[i, :, :, :]. It is mostly used in Numpy.

    • Assignment using slices. Some interesting example from the book:

      >>> l = list(range(10))
      >>> l
      [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
      >>> l[2:5] = [20, 30]
      >>> l
      [0, 1, 20, 30, 5, 6, 7, 8, 9]
      >>> del l[5:7]
      >>> l
      [0, 1, 20, 30, 5, 8, 9]
      >>> l[3::2] = [11, 22]
      >>> l
      [0, 1, 20, 11, 5, 22, 9]
      >>> l[2:5] = 100
      Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      TypeError: can only assign an iterable
      >>> l[2:5] = [100]
      >>> l
      [0, 1, 100, 22, 9]
  • + and * and augmented assignment on sequences

    • my_list = [[]] * 3 will result in a list with three references to the same inner list. List comprehension avoids this problem. [[] for i in range(3)].

    • += and *= will first try to use __iadd__ and fall back to __add__ and create a new object.

  • list.sort and sorted

    • list.sort sorts inplace and returns None.

    • sorted library function accepts any iterable object.

    • Both accept two parameters: reverse bool and key for the name of a function that produces sorting keys.

Misc