How to Override a Class Method in Python

28 Jan 2013

A class method in python differs from an instance method in a couple important ways:

  1. 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 usual self.
  2. 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:

  1. The python interpreter looks for Eggs in cls.__mro__, a tuple that represents the order in which the interpreter tries to match method and attribute names to classes.
  2. The interpreter checks the class dictionary for the next class that follows Eggs in that list that contains "parrot".
  3. The super object returns that version of "parrot", bound to cls, using the attribute-fetching __get__(cls) method.
  4. When Eggs.parrot calls this bound method, cls gets passed to Spam.parrot in place of the Spam 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.