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
cls
rather 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
Eggs
incls.__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
Eggs
in that list that contains"parrot"
. - The
super
object returns that version of"parrot"
, bound tocls
, using the attribute-fetching__get__(cls)
method. - When
Eggs.parrot
calls this bound method,cls
gets passed toSpam.parrot
in place of theSpam
class.
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.