[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