[Tutor] crazy black magic

Erik Price erikprice@mac.com
Sat Mar 15 13:38:01 2003


Just last week someone was asking about meta-class programming on this 
list.  I've been reading through "Python Cookbook" in my spare time, 
and it's really really good -- I'm learning a LOT about advanced 
techniques in Python (which is exactly what I was looking for when I 
bought the book, so I'm very satisfied so far) -- and I wanted to share 
this crazy code I just read, on page 55.  It's really not crazy in the 
literal sense ("insane"), what I really mean is that it's very powerful 
and works in ways that I do not normally think.  So I consider it 
pretty educational.

This is one way that you can subclass a parent class (in this case, 
"list", available in Python 2.2) and use meta-class programming 
(reflection) to wrap each of its methods without actually coding out 
those methods by hand.  (The purpose of the wrap is to add some code to 
them, in this case to reset a simple flag.)  The recipe is credited to 
Alex Martelli.

class FunkyList(list):
   def __init__(self, initlist=None):
     list.__init__(self, initlist)
     self._simple_flag = 0

def _wrapMethod(methname):
   _method = getattr(list, methname)
   def wrapper(self, *args):
     # reset simple flag to 0, then delegate
     # remaining method coding to base class
     self._simple_flag = 0
     return _method(self, *args)
   setattr(FunkyList, methname, wrapper)

for meth in 'setitem delitem setslice delslice iadd'.split(' '):
   _wrapMethod('__%s__' % meth)

for meth in 'append insert pop remove extend'.split(' '):
   _wrapMethod(meth)

del _wrapMethod

Here's my interpretation of how it works:  The class itself (FunkyList) 
simply subclasses the regular list class and adds a simple flag (which 
could be used for any purpose) to the class.  Note the constructor 
calls the parent constructor, to make sure that any code in the parent 
constructors get called as well (this is always a good idea when 
subclassing).

Then a throwaway function called "_wrapMethod" is defined, which, when 
passed a string containing a method name, fetches the object 
representing that method from the parent class (list), and calls 
"wrapper", a quickie-function that simply resets the simple flag and 
then returns that very method, which is then added to the derived class 
(FunkyList).  The end result of this is that the flag is reset and the 
method is added to FunkyList.  This is a great way to extend a parent 
class and add a bunch of methods.  The only drawback to it is that it's 
not immediately obvious what is going on, which is kind of 
anti-pythonic.  In contrast, the benefit (as Martelli points out in the 
recipe itself) is that it's unlikely that a typo or mistake will be 
made in reproducing the "boilerplate" of the parent class.  Both are 
good points, I think.

The throwaway function "_wrapMethod" is called on every method which 
must be added to the subclass.  I bet there's a way to introspect the 
parent class (list) and determine all of its methods, then simply 
iterate over this list, but I'm guessing there's probably a reason why 
Martelli didn't use that particular technique.

Finally the throwaway function is actually thrown away.  I would never 
have thought of doing this, and it makes sense (because you don't want 
it to stick around in to be accidentally called by future client code).

This isn't even the recipe's main point -- the main point is to give a 
high-performance means of doing a membership test on a sequence -- but 
I thought that this tiny chunk of code was valuable enough to warrant 
its own recipe!  Namely, you can use the meta-class 
programming/reflection/"black magic" to save yourself a lot of work and 
potential mistake-making when subclassing large parent classes like 
"list".

This is a great book so far.



Erik





-- 
Erik Price

email: erikprice@mac.com
jabber: erikprice@jabber.org