Fluent Python - Chapter 1. The Python Data Model

You can think of the data model as a description of Python as a framework. It formalizes the interfaces of the building blocks of the language itself, such as sequences, iterators, functions, classes, context managers, and so on.

A Pythonic Card Deck

Special methods: __getitem__ and __len__.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import collections

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

class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = 'spades diamonds clubs hearts'.split()

def __init__(self):
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]

nametuple can be used to build classes of objects that are just bundles of attributes with no custom methods.

1
2
beer_card = Card('7', 'diamonds')
beer_card

len() function returns the number of cards in it.

1
2
deck = FrenchDeck()
len(deck)

index operation, provided by __getitem__ method.

1
deck[0]
Card(rank='2', suit='spades')
1
deck[-1]
Card(rank='A', suit='hearts')

Get a random item from a sequence

1
2
from random import choice
choice(deck)
1
choice(deck)
Card(rank='9', suit='hearts')

Slicing support, because __getitem__ delegates to the [] oeprator of self._cards

1
deck[:3]
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
1
deck[12::13]
[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]

__getitem__ method make the deck iterable

1
2
for card in deck:
print(card)
1
2
3
4
Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
...

Iterated in reverse

1
2
for card in reversed(deck):
print(card)
1
2
3
4
Card(rank='A', suit='hearts')
Card(rank='K', suit='hearts')
Card(rank='Q', suit='hearts')
...

If a collection has no __contains__, the in oeprator does a sequential scan.

1
Card('Q', 'hearts') in deck
True
1
Card('7', 'beasts') in deck
False

Sorting

1
2
3
4
5
6
7
8
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):
print(card)
Card(rank='2', suit='clubs')
Card(rank='2', suit='diamonds')
Card(rank='2', suit='hearts')
...

How Special Methods Are Used

Special methods are meant to be called by Python intepreter, and not by you. You don’t write my_object.__len__(). You write len(my_object) and, then Python calls the __len__ instance method you implemented.

i in x: invocates iter(x), which in turn may call x.__iter() if that is available.

It is usually bette to call the relted built-in function, len, iter, str, etc. These built-ins call the corresponding special method.

Emulatin Numeric Types

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from math import hypot
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y

def __repr__(self):
return "Vector(%r, %r)" % (self.x, self.y)

def __bool__(self):
return bool(abs(self))

def __add__(self, other):
x = self.x + other.x
y = self.y + other.y
return Vector(x, y)

def __abs__(self):
return hypot(self.x, self.y)

def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
1
2
3
v1 = Vector(4, 1)
v2 = Vector(3, 4)
v1 + v2
Vector(7, 5)
1
2
v = Vector(3, 4)
abs(v)
5.0
1
v * 3
Vector(9, 12)
1
abs(v * 3)
15.0
1
bool(v)
True

String Representation

Use %r to obtain the standard representation

str() will call __repr__ as a fallback, if __str__ is not available.

__repr is to be unambiguous.

__str__ is to be readable.

Arithmetic Operators

__add__ and __mul__ return new instance, not touching either operand.

Boolean Value of a Custom Type

Here we return the magnitude of the vector.

bool(x) calls x.__bool__(). If x.__bool__() is not implemented, call x.__len__(), zero returns False. Otherwise bool returns True.

Overview of Special Methods

Table 1-1. Special method names (operators excluded)

Category Method names
String/bytes representation __repr__, str, format, bytes
Conversion to number __abs__, bool, complex, init_, float, hash, index
Emulating collections __len__, getitem, setitem__, delitem, contains, iter, reversed next
Iteration __iter__, reversed, next
Emulating callables __call__
Context management __enter__, exit
Instance creation and destruction __new__, init, del
Attribute management __getattr__, getattribute__, setattr, delattr, dir
Attribute descriptors __get__, set, delete
Class services __prepare__, instancecheck, subclasscheck

Table 1-2. Special method names for operators

Category Method names and related oeprators
Unary numeric operators __neg__, pos, abs
Rich comparison operators __lt__>, le__<=, __eq__==, __ne!=, __gt__>, __ge__>=
Arithmeric operators __add__+, sub-, __mul__*, __truediv__/, __floordiv__//, __mod__%, __divmod__divmod() __pow__** or pow(), __round__round()
Reversed arithmeric operators __radd__, rsub, rmul, rtruediv, rfloordiv, rmode, rdivmod, rpow
Augmented assignment arithmeric operators __iadd__, isub, imul, itrediv, ifloordiv, imod, ipow
Bitwise operators __invert__~, lshift__, __and__&, __or, __xor__^
Reversed Bitwise operators __rlshift__, rrshift, rand, ror, rxor
Augmented Bitwise operators __ilshift__, irshift, iand, ior, ixor

Why len Is Not a Method

“The Zen of Python”: “Practicality beats purity”

len(x) reads from a filed in a C struct of CPython, when x is a built-in type.

You can still customize it through __len__.