Close

Python Special Methods

In Python, methods or attributes that begin with _ are considered ‘private’. There isn’t public/private/protected levels of access as there are in some other languages, but a single _ is an indication that the method or attribute is not part of the public API. There is further meaning for things that start with two of them (__). For attributes, this introduces name mangling which I’m not going to go into here. Methods which start and end with __ are called “dunder” methods. you’ve probably already written some of these. Specifically, if you’ve written a class which needed some initialization, you’ve probably written a __init__ method.

Python operators like: +, -, *, /, **, [], (), ., etc. are bound to these “dunder” methods.

Let’s start with a basic example.

First let’s make a class:

In [1]: class foo(object):
   ...:     pass
   ...:

This doesn’t do much yet. Lets put some attributes on it:

In [2]: foo.a = 'aaa'

In [3]: foo.b = 'bbb'

Now let’s make an instance and take a look at those attributes:

In [4]: f = foo()

In [5]: f.a
Out[5]: 'aaa'

In [6]: f.b
Out[6]: 'bbb'

So far so good.

What if we wanted to be able to access the attributes of our class using dictionary lookup syntax?
Our class doesn’t currently support this:

In [7]: f['a']
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-43ec18acbe75> in <module>()
----> 1 f['a']

TypeError: 'foo' object has no attribute '__getitem__'

That error message is actually quite helpful. All we need to do is implement __getitem__:

This is really simple:

In [8]: foo.__getitem__ = foo.__getattribute__

In [9]: f['a']
Out[9]: 'aaa'

Awesome! How about assignment with dictionary syntax?

Again, our class does not currently support that …

In [10]: f['a'] = 'zzz'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-10-194e98347d7e> in <module>()
----> 1 f['a'] = 'zzz'

TypeError: 'foo' object does not support item assignment

But the fix is just as simple:

In [11]: foo.__setitem__ = foo.__setattr__

In [12]: f['a'] = 'zzz'

In [13]: f['a']
Out[13]: 'zzz'

In [14]: f['another_attribute'] = 42

In [15]: f.another_attribute
Out[15]: 42

Sweet! Now we can access and set values as if our class is a dictionary (and we can still use the . syntax as well).

Hopefully by now you’ve notices that square bracket [] access and assignment operations are implemented in the __getitem__ and __setitem__ functions.

This opens up a lot of possibilities! Say you wanted a callback function to fire whenever an attribute is accessed. Just put it in the __getattribute__ function!

Let’s write a new class:

In [16]: class bar(object):
   ....:     def __init__(self, callback):
   ....:         self.callback = callback
   ....:     def __getattribute__(self, name):
   ....:         self.callback(name)
   ....:         return self.__dict__[name]

Let’s make a callback function and make and instance of bar:

In [17]: def callback(name):
   ....:     print "in the callback, name was:", name
   ....:
   In [18]: b = bar(callback)

Ok, let’s take it for a spin!

In [19]: b.a = 42

In [20]: b.a
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-20-76ea6bf73769> in <module>()
----> 1 b.a

<ipython-input-16-e3cb6dbfcabe> in __getattribute__(self, name)
      3         self.callback = callback
      4     def __getattribute__(self, name):
----> 5         self.callback(name)
      6         return self.__dict__[name]
      7

<many, many pages of this>


<ipython-input-16-e3cb6dbfcabe> in __getattribute__(self, name)
      3         self.callback = callback
      4     def __getattribute__(self, name):
----> 5         self.callback(name)
      6         return self.__dict__[name]
      7

RuntimeError: maximum recursion depth exceeded

Oh noes! What happened there?

Remember, the . in self.callback is handled by __getattribute__ … and we were using self.callback (as well as self.__dict__) in the implementation of __getattribute__ . Oops.

That’s OK, there is a fix. We’ll just use the __getattribute__ method on the superclass!

In [21]: class bar(object):
   ....:     def __init__(self, callback):
   ....:         self.callback = callback
   ....:     def __getattribute__(self, name):
   ....:         object.__getattribute__(self, 'callback')(name)
   ....:         return object.__getattribute__(self, name)
   ....:

In [22]: b = bar(callback)

In [23]: b.a = 42

In [24]: b.a
in the callback, name was: a
Out[24]: 42

Sweet is the taste of victory!

There are a bunch of these functions that have special meaning. If you implement them in your classes, you can enhance the functionality and elegance of your code—just take care to follow the principle of least astonishment.

1 thought on “Python Special Methods

Leave a Reply

Your email address will not be published. Required fields are marked *