Hide
Bored? Check out the Recent Activity on Siafoo Join Siafoo Now or Learn More

Dynamic Superclassing in Python Atom Feed 0

Mucking with builtins is fun the way huffing dry erase markers is fun. Things are very pretty at first, but eventually the brain cell lossage will more than outweigh that cheap thrill

What you're getting into:

Mucking with builtins is fun the way huffing dry erase markers is fun. Things are very pretty at first, but eventually the brain cell lossage will more than outweigh that cheap thrill.

Barry Warsaw, 23 Mar 2000

Now, let's muck with some builtins.

How should a Python library provide for extensions? I'm working on a little system that has a nice object model. I would like to make it possible for people to extend the base objects with custom methods for doing whatever weird stuff people like to do.

Let us have a module, biz.py, with the following class definition:

# 's
1class A:
2 def __init__(self):
3 self.x = 5
4 self.y = 10
5
6 def foo(self):
7 print self.x

Now, let’s say we want to add a special bar method that would be kind of like the foo method but would print x * 3.14. We want to be able to do this from outside the original biz module; let’s say nuge.py:

# 's
1from biz import A
2def bar(self):
3 print self.x * 3.14
4A.bar = bar

Let’s give it a try:

# 's
1>>> import biz, nuge
2>>> a = biz.A()
3>>> a.foo()
45
5>>> a.bar()
615.7000000000000001

Note that we never actually use anything from nuge but we need to import it so that it can molest biz.A. Remember that module level statements are executed when the module is imported.

It’s also kind of interesting that you can modify the base classes of a class at anytime. Instead of adding a single method, it’s possible to add an entire class (or set of classes) into the **bases** chain, effectively grafting two (or more) classes together. Or, more precisely, dynamically superclassing A with B.

Let us redine nuge.py as follows:

# 's
1class B:
2 def bar(self):
3 print self.x * 3.14
4
5 def baz(self):
6 print self.x ** 2
7# this magic moment..
8A.__bases__ = (B,)

This is equivelant to specifiying B as superclass when we define A:

# 's
1class A(B):
2 ...

The advantage is that we can do this at runtime without modifying A’s source. The effect is that B becomes a superclass of A and B’s methods are thus available on all A instances:

# 's
 1>>> import biz, nuge
2>>> a = biz.A()
3>>> a.foo()
45
5>>> a.bar()
615.7000000000000001
7>>> a.baz()
825
9>>> isinstance(a,B)
10True

I should note that assigning the tuple (B,) to A. bases overwrites the original, declared superclass(es). It is much wiser to combine the original bases value with the new class as follows:

# 's
1A.__bases__ += (B,)

This appends B to the existing set of bases instead of just destroying them.

Some Code

Here’s the code I used to figure this crap out. Note that you will never find documentation that tells you all this, you have to play around .

# 's
 1class A:
2 def __init__(self):
3 self.x = 5
4
5 def foo(self):
6 print self.x
7
8def bling(self):
9 print self.x - self.x
10
11class B:
12 def bar(self):
13 print self.x * 3.14
14
15 def baz(self):
16 print self.x ** 2
17
18A.__bases__ += (B,)
19
20a = A()
21a.foo()
22a.bling()
23a.bar()
24a.baz()
25print isinstance(a,B)

Notes

  1. This isn’t a general purpose prototyping feature of Python. Most core Python classes and types do not allow assignment to bases.
  2. This should never , ever even be considered when you can use the pure subclassing mechanisms of Python.
  3. And even when you think you need to apply this technique, it is probably better to not if there is some other way. For instance, you can very easily and cleanly write functions that take the instance as a parameter.