Python's double underscore (aka dunder) methods (ex. __len__
) aren't magic method but more than that. Each of python's built-in 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
So whats the big deal? Ever wonder why python's len
method accept almost anything?
>>> len(range(5)) 5
Now imagine a simple class like this
>>> class Foo: ... pass ...
What would it return if I provide a Foo
instance to len
method?
>>> 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.
>>> 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
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.
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
Object comparison
We can implement __eq__
to compare between objects.
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
Object attribute access
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
Emulating container/collection
We can also treat objects as collection and do all sort of function call that accept a collection.
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 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, feel free to play with all other dunder methods and share what you have learned with us.
Top comments (0)