# Fluent Python summary

## Chapter 1. The Python Data Model

* the data model formalizes the interfaces of the building blocks of the language (sequences, iterators, functions, classes, context managers, etc)

> “For example, the syntax obj\[ key] is supported by the `__getitem__` special method. In order to evaluate my\_collection\[ key], the interpreter calls `my_collection.__getitem__( key)`.

* **namedtuple** can be used to build classes of objects that are just bundles of attributes with no custom methods, like a database record
* **defining `__len__`** for a class means that after you create an instance **you can utilize the data model’s standard len(deck\_instance)**
* defining **`__getitem__`** means that you can utilize deck\_instance\[-1] or deck\_instance\[0], etc
* since FrenchDeck has `__len__` and `__getitem__` methods, it can utilize Python’s built-in methods for sequences, like random.choice: `random.choice(deck)`
* just by **implementing `__getitem__`, our deck now supports slicing, iteration ,sorting**
* The only special method that is frequently called by user code directly is **`__init__`**, to invoke the initializer of the superclass in your own `__init__` implementation”
* Example 1-2 is a *Vector Class* that utilizes `__repr__`, `__abs__`, `__bool__`, `__add__` and `__mul__` to support Python’s built-in number methods

```python
import collections

Card = collections.namedtuple('Card', ['rank', 'suit'])


class FrenchDeck:
    """
    Test of sequence properties:

        >>> beer_card = Card('7', 'diamonds')
        >>> beer_card
        Card(rank='7', suit='diamonds')
        >>> deck = FrenchDeck()
        >>> len(deck)
        52
        >>> deck[0]
        Card(rank='2', suit='spades')
        >>> deck[-1]
        Card(rank='A', suit='hearts')
        >>> from random import choice
        >>> choice(deck)  # doctest: +ELLIPSIS
        Card(rank=..., suit=...)
        >>> deck[:3]  # doctest: +NORMALIZE_WHITESPACE
        [Card(rank='2', suit='spades'), Card(rank='3', suit='spades'),
        Card(rank='4', suit='spades')]
        >>> deck[12::13]  # doctest: +NORMALIZE_WHITESPACE
        [Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'),
        Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]
        >>> for card in deck:  # doctest: +ELLIPSIS
        ...     print(card)
        Card(rank='2', suit='spades')
        Card(rank='3', suit='spades')
        ...
        >>> for card in reversed(deck):  # doctest: +ELLIPSIS
        ...     print(card)
        Card(rank='A', suit='hearts')
        Card(rank='K', suit='hearts')
        ...
        >>> Card('Q', 'hearts') in deck
        True
        >>> Card('7', 'beasts') in deck
        False
        >>> suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0)
        >>> def spades_high(card):
        ...     rank_value = FrenchDeck.ranks.index(card.rank)
        ...     return rank_value * len(suit_values) + suit_values[card.suit]
        >>> for card in sorted(deck, key=spades_high):  # doctest: +ELLIPSIS
        ...     print(card)
        Card(rank='2', suit='clubs')
        Card(rank='2', suit='diamonds')
        ...

    Test of monkey patching:
    - def: change a class or module at runtime, without touching the source code
    - the code is often tightly coupled with the program, often handling private
      and undocumented parts
    - use monkey patching to make it mutable and compatible with random.shuffle

        >>> def set_card(deck, position, card):
        ...     deck._cards[position] = card
        ...
        >>> FrenchDeck.__setitem__ = set_card
        >>> from random import shuffle
        >>> shuffle(deck)
        >>> deck[:5]  # doctest: +ELLIPSIS
        [Card(rank=...
    """
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suits = 'spades diamonds clubs hearts'.split()

    def __init__(self):
        """Line breaks are ignored inside pairs of [], {}, or (). so you can build
        multiline lists, listcomps, genexprs, dicts without using \ line continuation escape"""
        self._cards = [Card(rank, suit) for suit in self.suits
                       for rank in self.ranks]

    def __len__(self):
        return len(self._cards)

    def __getitem__(self, position):
        return self._cards[position]
```

| Magic Method                      | Example                               | Explanation                               |
| --------------------------------- | ------------------------------------- | ----------------------------------------- |
| `__new__(cls [,...])`             | `instance = MyClass(arg1, arg2)`      | `__new__` is called on instance creation  |
| `__init__(self [,...])`           | `instance = MyClass(arg1, arg2)`      | `__init__` is called on instance creation |
| `__cmp__(self, other)`            | `self == other`, `self > other`, etc. | Called for any comparison                 |
| `__pos__(self)`                   | `+self`                               | Unary plus sign                           |
| `__neg__(self)`                   | `-self`                               | Unary minus sign                          |
| `__invert__(self)`                | `~self`                               | Bitwise inversion                         |
| `__index__(self)`                 | `x[self]`                             | Conversion when object is used as index   |
| `__nonzero__(self)`               | `bool(self)`                          | Boolean value of the object               |
| `__getattr__(self, name)`         | `self.name # name doesn't exist`      | Accessing nonexistent attribute           |
| `__setattr__(self, name, val)`    | `self.name = val`                     | Assigning to an attribute                 |
| `__delattr__(self, name)`         | `del self.name`                       | Deleting an attribute                     |
| `__getattribute__(self, name)`    | `self.name`                           | Accessing any attribute                   |
| `__getitem__(self, key)`          | `self[key]`                           | Accessing an item using an index          |
| `__setitem__(self, key, val)`     | `self[key] = val`                     | Assigning to an item using an index       |
| `__delitem__(self, key)`          | `del self[key]`                       | Deleting an item using an index           |
| `__iter__(self)`                  | `for x in self`                       | Iteration                                 |
| `__contains__(self, value)`       | `value in self`, `value not in self`  | Membership tests using `in`               |
| `__call__(self [,...])`           | `self(args)`                          | "Calling" an instance                     |
| `__enter__(self)`                 | `with self as x:`                     | `with` statement context managers         |
| `__exit__(self, exc, val, trace)` | `with self as x:`                     | `with` statement context managers         |
| `__getstate__(self)`              | `pickle.dump(pkl_file, self)`         | Pickling                                  |
| `__setstate__(self)`              | `data = pickle.load(pkl_file)`        | Pickling                                  |

**Conclusion:** “By implementing **special methods**, your objects can behave like the built-in types, enabling the expressive coding style the community considers Pythonic.”

## Chapter 2. An Array of Sequences

* Strings, lists, byte sequences, arrays, XML elements, and database results share a rich set of common operations including iteration, slicing, sorting, and concatenation
* Understanding the variety of sequences available in Python saves us from reinventing the wheel, and their common interface inspires us to create APIs that properly support and leverage existing and future sequence types.
* **container sequences**
  * **hold items of different types**
  * e.g. list, tuple, collections.deque
  * hold references to the containing objects
* **flat sequences**
  * **hold items of one type**
  * e.g. str, bytes, bytearray, memoryview, array.array
  * physically store the value of each item in their own memory space
* **m*****utable sequences:*** list, bytearray, array.array, collections.deque, memoryview
* **immutable sequences:** tuple, str, bytes
* list comprehensions are *more explicit* than for-loop constructs, because list comprehensions are meant to do only one thing
* “Listcomps do everything the map and filter functions do, without the contortions of the functionally challenged Python lambda”

### **Generator Expressions**

* similar to list expressions for building tuples, arrays and other types of sequences
* e.g. `tuple(ord(symbol) for symbol in symbols)`
* use the **same syntax as listcomps, but are enclosed in parentheses rather than brackets**

### **Tuples**

* Not just “immutable lists”
* can be used also as **records** with no field names
* each item in the tuple holds the data for one field and the position of the item gives its meaning
* when using a tuple as a collection of fields, the number of items is often fixed and their order is always vital
* *Tuples work well as records because of the **tuple unpacking mechanism***
* **collections.namedtuple:** factory that produces subclasses of *tuple* enhanced with field names, a class name and some extra attributes / methods

#### **Tuple unpacking allows you to:**

* elegantly swap values w/o a temporary variable: `b, a = a, b`
* prefixing an argument with a star when calling a function: `t = (2, 4)` ; `add(*t)`
* grabbing excess items in Python 3: a, \*body, c, d = range( 5)
* perform nested tuple unpacking: `for name, cc, pop, (latitude, longitude) in metro_areas:` will give access to *latitude* and *longitude* as individual values of a tuple inside of metro\_areas

**Slicing**

* slicing operations “are more powerful than most people realize”
* slice and range exclude the last item
  * “It’s easy to see the length of a slice or range when only the stop position is given: range( 3) and my\_list\[: 3] both produce three items”
  * “It’s easy to compute the length of a slice or range when start and stop are given: just subtract stop - start”
  * “It’s easy to split a sequence in two parts at any index x, without overlapping: simply get my\_list\[: x] and my\_list\[ x:]”
* case: s\[ a:b:c] can be used to specify a stride or step c, causing the resulting slice to skip items
  * ‘bicycle’\[::3] => ‘bye’
  * ‘bicycle’\[::-1] => ‘elcycib’
  * ‘bicycle’\[::-2] => ‘eccb’
* “Mutable sequences can be grafted, excised, and otherwise modified in place using slice notation on the left side of an assignment statement or as the target of a del statement” :
  * l = range(10)
  * l\[2:5] = \[20, 30]
  * l is now \[0, 1, 20, 30, 5, 6, 7, 8, 9]
  * del l\[5:7]
  * l is now \[0, 1, 20, 30, 5, 8, 9]

**Sorting**

* “functions or methods that change an object in place should return None to make it clear that the object itself was changed, and no new object was created”
* ***list.sort*** sorts a list in-place and returns None
* ***sorted*** accepts any iterable object, creates a new *list* and returns it
* ***bisect( haystack, needle)*** does a binary search for needle in haystack — which must be a sorted sequence - to locate the position where needle can be inserted while maintaining haystack in ascending order.
  * “An interesting application of bisect is to perform *table lookups by numeric values* - for example, to convert test scores to letter grades” ![example 2-18](http://kevinriggen.com/img/fluent-python/ex2-18.png)
* Sorting is expensive, so once you have a sorted sequence, it’s good to keep it that way. That is why *bisect.insort* was created.
* **insort(seq, item**) inserts item into seq so as to keep seq in ascending order

### Alternatives to Lists

* **If the list will only contain numbers, an array.array is more efficient**
* The built-in memoryview class is a shared-memory sequence type that lets you handle slices of arrays without copying bytes -“A memoryview is essentially a generalized NumPy array structure in Python itself (without the math). It allows you to share memory between data-structures (things like PIL images, SQLlite databases, NumPy arrays, etc.) without first copying. This is very important for large data sets.”
* collections.deque is a thread-safe double-ended queue designed for fast inserting and removing from both ends (its expensive to move left-sided items from a list)
* “It is also the way to go if you need to keep a list of “last seen items” or something like that, because a deque can be bounded — i.e., created with a maximum length — and then, when it is full, it discards items from the opposite end when you append new ones.”

## Chapter 3. Dictionaries and Sets <a href="#chapter-3-dictionaries-and-sets" id="chapter-3-dictionaries-and-sets"></a>

* the *dict* type is a fundamental part of Python: module namespaces, class and instance attributes and function keyword arguments use them
* ***hash tables*****&#x20;are the engines behind dicts and sets**

*Methods of the mapping types* dict, collections.defaultdict and collections.OrderedDict; optional arguments are enclosed in \[…] ![table 3-1](http://kevinriggen.com/img/fluent-python/table3-1.png) ![table 3-1](http://kevinriggen.com/img/fluent-python/table3-1-contd.png)

> “The way update handles its first argument m is a prime example of **duck typing**: it first checks whether m has a keys method and, if it does, assumes it is a mapping. Otherwise, update falls back to iterating over m, assuming its items are (key, value) pairs.”

* ***setdefault*** is convenient if you want **to update the value of a key that may not exist yet**: `books.setdefault('sci-fi', []).append('anathem')`
* the ***defaultdict*** class or a mapping-type class with **`__missing __`** overridden are other ways to handle missing keys in a mapping object (e.g. to convert numeric-key lookups to strings before the lookup in a project that deals with circuits and allows looking up numbers for convenience)

### **Alternatives to&#x20;*****dict***

* **collections.OrderedDict** maintains keys in insertion order
* **collections.ChainMap** “holds a list of mappings that can be searched as one”
* ***collections.Counter*** holds an integer count for each key
  * `>>> ct = collections.Counter("abracadabra")`
  * `>>> ct`
    * \=> Counter({‘ a’: 5, ‘b’: 2, ‘r’: 2, ‘c’: 1, ‘d’: 1})\`
* **collections.UserDict**
  * used to create new mapping types, as its more convenient to extend than the standard `dict`
* **types.MappingProxyType** builds an immutable mappingproxy instance from a dict

### **Set Theory**

* there is ***set*** and there is the immutable ***frozenset***
* **a set is a collection of unique objects**
* **elements** in a set must be **hashable**
* the **union, intersection, difference and other mathematical operators** can be determined between multiple sets
* *counting occurrences of needles in a haystack for any iterable*: `len(set(needles) & set(haystack))`
* `s = {1}` creates a set
* `s = {}` creates an empty dict
* `s = set()` creates an empty set
* **set comprehensions:** `{chr( i) for i in range( 32, 256) if 'SIGN' in name( chr( i),'')}`
* dicts and sets are fast, and use hash tables under the hood
* “If your program does any kind of I/ O, t**he lookup time for keys in dicts or sets is negligible, regardless of the dict or set**”

![table 3-6](http://kevinriggen.com/img/fluent-python/table3-6.png)

## Chapter 4. Text versus Bytes

> “Humans use text. Computers speak bytes”

> “This chapter deals with Unicode strings, binary sequences, and the encodings used to convert between them.”

To be continued…

## Chapter 5. First-Class Functions

&#x20;**Functions are “first-class objects”** and therefore can be:

* created at runtime
* **assigned to a variable** or element in a data structure
* **passed as an argument** to a function
* **returned as the result of a function**
* in other words, **functions are objects**
* **higher-order functions are functions that take other functions as arguments**
* there are seven callable objects in Python:
  * user-defined functions
  * built-in functions implemented in C, like `len` or `time.strftime`
  * built-in methods like `dict.get`
  * methods of classes
  * classes
  * class instances, if they define a `__call__` method
  * generator functions
* arbitrary Python objects can be made to behave like functions if you define a `__call__` instance method for them

![example 5-26](http://kevinriggen.com/img/fluent-python/ex5-26.png)

![example 5-27](http://kevinriggen.com/img/fluent-python/ex5-27.png)

## Chapter 6. Design Patterns with First-Class Functions

> ‘Although design patterns are language-independent, that does not mean every pattern applies to every language. In his 1996 presentation, “Design Patterns in Dynamic Languages”, Peter Norvig states that 16 out of the 23 patterns in the original Design Patterns book by Gamma et al. become either “invisible or simpler” in a dynamic language’

This chapter runs through some implementations of the [Strategy](https://en.wikipedia.org/wiki/Strategy_pattern) and [Command](https://en.wikipedia.org/wiki/Command_pattern) design patterns. I don’t feel comfortable summarizing it: it’d be a good idea to read through this chapter a few times a year.

## Chapter 7. Function Decorators and Closures <a href="#chapter-7-function-decorators-and-closures" id="chapter-7-function-decorators-and-closures"></a>

> “The end goal of this chapter is to explain exactly how function decorators work, from the simplest registration decorators to the rather more complicated parameterized ones.”

**Decorator:** a callable that takes another function as argument (the decorated function). The decorator may perform some processing with the decorated function, and returns it or replaces it with another function or callable objects.

Decorators are executed as soon as the module is imported, but the decorated function only run when they are explicitly invoked.

* decorators run right after the decorated function is defined (usually at import time)
* a good use of decorators is to register some function as belonging to a group without needing to define it as part of the group in another part of the code base
  * for example, if there are multiple “promotion” types (bulk-item discount, good-customer discount) in an e-commerce application written as functions, you can apply a @promotion decorator to the functions, and create an inventory at runtime, rather than having to account for each function name individually

<br>
