Python data model revisited
Tags: python, dunder
Published: 2019-02-17 06:17:05

---
title: Python data model revisited
published: true
description: Python data model revisited, also known as dunder (shortcut of double underscore) method 
tags: python, dunder
---

Python's double underscore (aka dunder) methods (ex. `__len__`) aren't magic method but more than that. Each of python's [built-in](https://docs.python.org/3/library/functions.html) functions has a corresponding dunder method. Officially they are documented under python data model. Try google `python data model`, first result would point to python [docs](https://docs.python.org/3/reference/datamodel.html)

So whats the big deal? Ever wonder why python's `len` method accept almost anything? 

```python
>>> len(range(5))
5
```

Now imagine a simple class like this

```python
>>> class Foo:
...     pass
...
```

What would it return if I provide a `Foo` instance to `len` method?

```python
>>> f = Foo()
>>> len(f)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'Foo' has no len()
```

It throws an exception `TypeError` complaining `Foo` has no `len`. Right, if we implement a `__len__` on `Foo` object and return anything, python's `len` will look for a corresponding `__len__` method for that object.

```python
>>> class Foo:
...     def __len__(self):
...             return 5
...
>>> f = Foo()
>>> len(f)
5
```
boom! now it works and return what we want it to return by implementing the `__len__` method.

Other most common dunder methods are

#### String representation

```python
In [11]: class Point:
    ...:     def __init__(self, x, y):
    ...:         self.x = x
    ...:         self.y = y
    ...:
    ...:     def __str__(self):
    ...:         return 'Stringified Point: ({x}, {y})'.format(x=self.x, y=self.y)
    ...:
    ...:     def __repr__(self):
    ...:         return 'Representing Point: ({x}, {y})'.format(x=self.x, y=self.y)
    ...:

In [12]: p = Point(2,3)

In [13]: p
Out[13]: Representing Point: (2, 3)

In [14]: str(p)
Out[14]: 'Stringified Point: (2, 3)'
```

#### Operator overloading

We can implement binary arithmetic operations (ex `+`, `-` etc) implementing specific dunder methods.

```python
In [21]: class Point:
    ...:     # continuing from previous example   
    ...:     def __add__(self, other):
    ...:         return Point(self.x + other.x, self.y + other.y)
    ...:

In [22]: p1 = Point(1, 2)

In [23]: p2 = Point(3,5)

In [26]: p3
Out[26]: Representing Point: (4, 7)
```

To learn more about emulating numeric types checkout the [doc](https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types)

#### Object comparison

We can implement `__eq__` to compare between objects.

```python
In [36]: class Point:
    ...:     # continuing from previous example
    ...:     def __eq__(self, other):
    ...:         # custom comparison
    ...:         return self.x == other.x and self.y == other.y
    ...:
In [37]: p1 = Point(2, 3)

In [38]: p2 = Point(2, 3)

In [39]: p1 == p2
Out[39]: True

In [40]: p3 = Point(2, 4)

In [41]: p1 == p3
Out[41]: False
```

To check other object customization please check the [doc](https://docs.python.org/3/reference/datamodel.html#basic-customization)

#### Object attribute access

```python
In [44]: class Point:
    ...:     # continuing from previous example
    ...:     def __getattr__(self, attr):
    ...:         if (attr == 'X'):
    ...:             return self.x
    ...:         elif (attr == 'Y'):
    ...:             return self.y
    ...:         else:
    ...:             raise AttributeError('Point object has no attribute {attr}'.format(attr=attr))
    ...:
In [45]: p = Point(2, 3)

In [46]: p.x
Out[46]: 2

In [47]: p.X
Out[47]: 2

In [48]: p.Xx
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-48-9a49f22e192d> in <module>
----> 1 p.Xx

<ipython-input-44-f5f17ce1e8d6> in __getattr__(self, attr)
     23             return self.y
     24         else:
---> 25             raise AttributeError('Point object has no attribute {attr}'.format(attr=attr))
     26

AttributeError: Point object has no attribute Xx
```

More attribute access can be found in the [doc](https://docs.python.org/3/reference/datamodel.html#customizing-attribute-access)

#### Emulating container/collection

We can also treat objects as collection and do all sort of function call that accept a collection.

```python
In [49]: class Point:
    ...:     # continuing from previous example
    ...:     def __getitem__(self, index):
    ...:         if index == 0:
    ...:             return self.x
    ...:         elif index == 1:
    ...:             return self.y
    ...:         else:
    ...:             raise IndexError('No item found with index {index}'.format(index=index))
    ...:
In [50]: p = Point(2, 3)

In [51]: p[0]
Out[51]: 2

In [53]: p[1]
Out[53]: 3

In [54]: p[2]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-54-21c545b39f61> in <module>
----> 1 p[2]

<ipython-input-49-6dd2485f38f5> in __getitem__(self, index)
     31             return self.y
     32         else:
---> 33             raise IndexError('No item found with index {index}'.format(index=index))
     34

IndexError: No item found with index 2
```

Checkout the [doc](https://docs.python.org/3/reference/datamodel.html#emulating-container-types) for whole list of dunder methods to emulate collection.

**The best thing of python data model is that programmers hardly need to call the dunder method (except `__init__` and few other customization methods), python interpreter will call these methods as needed.**

I have put the complete `Point` class in this [gist](https://gist.github.com/shaikhul/316687d44952187875d4c10051f33190), feel free to play with all other dunder methods and share what you have learned with us.