How to Override a Class Method in Python
A class method in python differs from an instance method in a couple important ways:
- It binds to a class rather than an instance (hence its name). Thus, its first argument is a class, often called
clsrather than the usualself. - It can be called on both an instance of a class and the class itself.
In general, they behave similarly, but one area in which they can differ is when we go to override the class method:
class Spam(object):
@classmethod
def parrot(cls, message):
print cls.__name__, "says:", message
class Eggs(Spam):
@classmethod
def parrot(cls, message):
Spam.parrot(cls, message)
This code is broken because Spam.parrot is already bound to the Spam class. This means Spam.parrot’s cls argument will be the Spam class rather than the Eggs class that we wanted, so Spam.parrot will end up being called in the wrong context. Even worse, everything Eggs.parrot passed to it, including cls, ends up getting passed as regular arguments, resulting in disaster.
>>>> Spam.parrot("Hello, world!")
Spam says: Hello, world!
>>>> Eggs.parrot("Hello, world!")
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "<console>", line 4, in parrot
TypeError: parrot() takes exactly 2 arguments (3 given)
To chain up to the Spam class’s implementation we need to use a super object, which will delegate things the way we want.
class Eggs(Spam):
@classmethod
def parrot(cls, message):
super(Eggs, cls).parrot(message)
There’s a shortcut for the last line if you’re using python 3:
super().parrot(message)
The super object functions as a proxy that delegates method calls to a class higher up in the Eggs class’s hierarchy, and in this case it is critical in ensuring that it gets called in the right context.
>>>> Spam.parrot("Hello, world!")
Spam says: Hello, world!
>>>> Eggs.parrot("Hello, world!")
Eggs says: Hello, world!
If you haven’t used super() before, here’s what it’s doing:
- The python interpreter looks for
Eggsincls.__mro__, a tuple that represents the order in which the interpreter tries to match method and attribute names to classes. - The interpreter checks the class dictionary for the next class that follows
Eggsin that list that contains"parrot". - The
superobject returns that version of"parrot", bound tocls, using the attribute-fetching__get__(cls)method. - When
Eggs.parrotcalls this bound method,clsgets passed toSpam.parrotin place of theSpamclass.
In general I tend to stick with the older-style syntax for chaining method calls, but this is one case where super() is simply indispensable.