Thursday, January 1, 2009

MRO magic

Here's some more less publicized evil you can accomplish with metaclasses. Here's a simple example file: (Note that although I'm using Python 3.0, this works with all new-style class supporting versions.)

# mromagic.py
class A(object):

def a_method(self):
print("A")

class B(object):

def b_method(self):
print("B")

class MROMagicMeta(type):

def mro(cls):
return (cls, B, object)

class C(A, metaclass=MROMagicMeta):

def c_method(self):
print("C")


Now let's play with this a little:

>>> import mromagic
>>> mycls = mromagic.C()
>>> mycls
<mromagic.C object at 0x622890>
>>> mycls.c_method()
C
>>> mycls.a_method()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'C' object has no attribute 'a_method'
>>> mycls.b_method()
B
>>> type(mycls).__mro__
(<class 'mromagic.C'>, <class 'mromagic.B'>, <class 'object'>)
>>> type(mycls).__bases__
(<class 'mromagic.A'>,)


How does this work? By overriding mro() on the a metaclass we can define a custom __mro__ for our class. Python will then traverse it instead of the default implementation, which is provided by type.mro().