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 languages itself, such as sequences, iterators, functions, classes, context managers and so on.

Python allows operator overloading, which let us to define the custom behavior for a class. For instance, if a class defines a __str__(), then it can return a readable string representation of an object.

In this post, I will enumerate some special methods that can change the behavior of an object and may putted into practice in daily work.

The special names are always spelled with leading and trailing double underscore, i.e. __str__.

__str__

__str()__ is called by str(object) and built-in functions such as format() and print() for a nicely printable string of an object. the return value must be a string object.

The default implementation defined by the built-in type object calls __repr()__.

class Element:
    def __init__(self, k, v):
        self.key = k
        self.value = v

    def __str__(self):
        return '%s -> %s' % (self.key ,self.value)

c = Element('a', 'b')
print(c) # print a -> b

__repr__

__repr__() is called by repr() function to compute the string representation of an object.

The difference between __str__() and __repr__() is

  • __repr__() is for developers, and should be unambiguous.
  • __str__() is for customers, and should be readable.

__hash__

__hash() is called by built-in hash() for operations on members of hashed collections including set, frozenset and dict. __hash__() return an integer, and the objects that are equal should have the same hash value.

A class that overrides __eq__() and does not define __hash__() will have its __hash__() implicitly set to None.

__bool__

__bool__() by the built-in bool() is called to implement truth value testing. When this method is not defined, __len__() is called.

class Element:
    def __init__(self, k, v):
        self.key = k
        self.value = v

    def __bool__(self):
        return self.key != 0

a = Element(0, 'a')
if a: # False
    print('a') 

b = Element(1, 'b')
if b: # True
    print('b')

__len__

__len__() is called to implemented the build-in function len(). It should return the length of the object.

For built-in types like list, str, bytearray etc., the interpreter takes a shortcut: the CPython implementation of len() actually returns the value of the ob_size field in the PyVarObject struct that represents any variable-sized built-in object in memory.

__getitem__

__getitem__(self, key) is called to implement evaluation of self[key]. For sequence types, the accepted keys should be integers and slice objects.

from collections import namedtuple

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

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

    def __init__(self):
        self._cards = [Card(r, s) for s in self.suits for r in self.ranks]

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

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

d = Deck()
print(len(d)) # print 52

By implementing the special methods __len__ and __getitem__, our Deck behaves like a standard Python sequence, allowing it to benefit from the core language features - like iteration and slicing.

d = Deck()

# [Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
print(d[:3]) 

for card in reversed(d):
    print(card)

__missing__

__missing__(self, key) is called by dict.__getitem__() when key is not present.

__iadd__

__iadd__(self, other) implements addition with assignment. For instance, for a += b, __iadd__ might return a + b, which would be assigned to a.

class Element:
    def __init__(self, k, v):
        self.key = k
        self.value = v

    def __iadd__(self, other):
        self.key += other.key
        self.value += other.value
        return self

    def __str__(self):
        return '%d -> %s' % (self.key, self.value)

a = Element(1, 'a')
a += Element(2, 'b')
print(a) # 3 -> ab

__abs__

The abs built-in function returns the absolute value of integers and floats, and the magnitude of complex number. Actually, it is __abs__ that takes effect underneath.

from math import hypot
class Vector:
    def __init__(self, x=0,y=0):
        self.x = x
        self.y = y
    def __abs__(self):
        return hypot(self.x, self.y)

v = Vector(3, 4)
print(abs(v)) # 5.0

__iter__

This method is called when an iterator is required for a container. This method should return a new iterator object that can iterate over all the objects in the container.

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __iter__(self):
        return (i for i in (self.x, self.y))

v = Vector(2, 3)
x, y = v
print(x, y) # 2 3

Reference