Dugan Chen's Homepage

Various Things

Implementing Pythonic mixins with class decorators.

There was virtually no discussion following the posting of this PEP, meaning that everyone agreed it should be accepted.

Here is the correct and Pythonic way to do mixins in Python. You don’t. You use class decorators to do the same thing. Note that class decorators were added to Python as of 2.6. I suspect that that was after most mixin-using Python code was designed.

Here’s how you would mix in classes with foo and bar methods the old-fashioned way:

class FooMixin(object):
    def foo(self):
        pass


class BarMixin(object):
    def bar(self):
        pass


class FooBar(FooMixin, BarMixin):
    pass

You then have to deal with the complexities of multiple inheritance. Some have even argued that this should be considered harmful.

Here’s a better way to do it. You use class decorators, which are simply functions that take a class and return a class. We don’t want the decorator to affect the behavior of other instances, so we return a subclass instead of monkey patching the class itself. To give the subclass the same name as the class that was passed in, we use a type constructor to create it.

def add_foo(klass):
    def foo(self):
        pass
    return type(klass.__name__,
        (klass,), {'foo': foo})

def add_bar(klass):
    def bar(self):
        pass
    return type(klass.__name__, (klass,),
        {'bar': bar})

@add_foo
@add_bar
class FooBar(object):
    pass

Now you’re only dealing with single inheritance. You have a class, FooBar, with foo and bar methods.

You have a clear method resolution order (check with inspect.mro) leading from a class with both foo and bar, to a class with just bar, to a class with neither. In other words, you just read the class definition from top to bottom to figure out the mro.

>>> import inspect
>>> from pprint import pprint
>>> pprint(inspect.getmro(FooBar))
(<class '__main__.FooBar'>,
 <class '__main__.FooBar'>,
 <class '__main__.FooBar'>,
 <type 'object'>)

>>> methods = lambda i: [x for x
        in dir(inspect.getmro(FooBar)[i])
        if not x.startswith('__')]
>>> methods(0)
['bar', 'foo']
>>> methods(1)
['bar']
>>> methods(2)
[]

I think this is better. For one thing, your head won’t explode from trying to figure out the method resolution order that you get when you use multiple inheritance.