On twitter recently I was wondering what the best way to create a dictionary of functions in Python was. On a suggestion of Paul Hankin’s I looked into classes and metaclasses. The most direct way I discovered is to use a class; metaclass not required:
class foo: def bar(): pass def zon(x): return 1+x fundict = foo.__dict__
This is direct, but not at all obvious. Hint to people that are mystified already: the
__dict__ attribute of the class is a dictionary containing everything defined in the class.
fundict is now a dictionary that contains the two functions bar and zon.
I made this discovery a few days before EuroPython, and was fortunate enough to bump into Bruce Eckel in the hallway at EuroPython just before he gave his metaprogramming talk, so I showed it to him in what I call my “naughty decorator” form:
def asdict(x): return x.__dict__ @asdict class baz: def bar(): pass def zon(x): return 1+x
(By the way, one of the things I learnt at EuroPython is that the crappy decorator syntax gives Java refugees a warm fuzzy feeling).
Bruce pronounced this “not as naughty as you imply” and “worth showing” (this blog post was half-written before I bumped into Bruce, but he is definitely encouraging me).
It’s worth showing for the following reason:
The functions you get from the class’s
__dict__ dictionary are not the same as the methods you get by accessing the attributes of the class. In other words «
foo.bar is not foo.__dict__['bar']». I was surprised by this, and so was Bruce.
As well being a little weird, it makes a compact example to show the different between a function, an unbound method, and a bound method.
I hope I don’t need to introduce a function. It’s just a thing you call with some arguments. Where it differs from a method is that a method is regarded as a message sent to an object, and receives that object as its first argument.
In Python, an unbound method is a method that isn’t associated with any particular object; it requires an object as its first argument (and Python loses big here, by requiring the object to be an instance of the class that defined the method). A bound method is a method already associated with an object; that object becomes the method’s first argument when the bound method is invoked with the remaining arguments.
So if we define a class foo (as we did, above), then:
foo.bar is an unbound method;
foo().bar is a bound method (bound to the object we just created by invoking the foo class); and,
foo.__dict__['bar'] is a function.
This last fact was a great surprise to me. I had expected it to be an unbound method, and thought that my naughty decorator would have to have some hacky code to dig the function out of the unbound method. But it doesn’t.
Tiny problem: Using the asdict gives a dictionary that contains
__doc__ keys. Solution: another decorator:
def cleandict(x): for k in ('__module__', '__doc__'): del x[k] return x @cleandict @asdict class baz: def bar(): pass def zon(x): return 1+x