[Tutor] Metaclass programming

Kent Johnson kent37 at tds.net
Thu Sep 6 02:44:07 CEST 2007


Orest Kozyar wrote:
> You're right.  I just figured out that for some reason, when I use the
> SQLAlchemy mapper() function to map my classes to the corresponding table
> object, it seems to affect inspect.getargspec().  
> 
> For example:
> 
> from sqlalchemy.orm import mapper
> from sqlalchemy import Table, MetaData
> import inspect
> 
> class Foo:
> 	def __init__(self, x, y):
> 		pass
> 
> print inspect.getargspec(Foo.__init__)
> 
>>> (['self', 'x', 'y'], None, None, None)
> 
> metadata = MetaData()
> foo_table = Table('foo', metadata)
> mapper(Foo, foo_table)
> 
> print inspect.getargspec(Foo.__init__)
> 
>>> (['instance'], 'args', 'kwargs', None)
> 
> I'm not sure why this would be the case, but is a bit frustrating since I do
> need the names of the positional arguments sometimes.

I can't be sure without looking at the code but it looks like SA is 
decorating the class methods; this usually loses the signature of the 
method. The problem is described here:
http://www.phyast.pitt.edu/~micheles/python/documentation.html#statement-of-the-problem

>> Why are you doing this?
> 
> Partially as an exercise to help me better understand Python inspection as
> well as metaclass programming.

Note that there is no metaclass in the above code, unless mapper() is 
introducing one behind the scene...

   I also am using it in a EntitySingleton
> metaclass that I adapted from the SQLAlchemy wiki
> (http://www.sqlalchemy.org/trac/wiki/UsageRecipes/UniqueObject).  Some of my
> classes have unique constraints, and I want the metaclass to check these
> unique constraints and return an object from the database if an object
> meeting these constraints already exists.  
> 
> For example:
> 
> class User:
> 	__unique__ = ['firstname', 'lastname']
> 	__metaclass__ = EntitySingleton
> 
> 	def __init__(self, firstname, lastname, password):
> 		pass
> 
> The metaclass knows what the "unique" constraints are based on the
> __unique__ list, but needs to use inspect.getargspec() to get the variable
> names and match them up with the *args list that EntitySingleton.__call__
> recieves.  At least, that's how I'm trying to do it, but I expect this might
> not be the best way to do it?  Either case, when the class is mapped to a
> database table, I lose all this information, so will need to figure out a
> different way of approaching this.

You should probably look for a different way to do it. Depending on your 
tolerance for hacks, it may be possible to work around the decorator. 
The original function is probably contained within the closure of the 
decorator and you can dig it out. For example,

make a simple decorator and decorate a function:

In [5]: def deco(f):
    ...:     def _deco(*args, **kwds):
    ...:         print 'about to call', f.__name__
    ...:         f(*args, **kwds)
    ...:     return _deco
    ...:
In [6]: @deco
    ...: def f(): print 'foo here'
    ...:

Hey, it works!

In [7]: f()
about to call f
foo here

but the decorated function has the signature of the decoration:

In [8]: import inspect
In [9]: inspect.getargspec(f)
Out[9]: ([], 'args', 'kwds', None)

If we dig hard enough we can find the original and get its signature:

In [11]: f.func_closure
Out[11]: (<cell at 0x2087d70: function object at 0x20ad2f0>,)

In [14]: f.func_closure[0].cell_contents
Out[14]: <function f at 0x20ad2f0>
In [15]: inspect.getargspec(f.func_closure[0].cell_contents)
Out[15]: ([], None, None, None)
In [16]: f.func_closure[0].cell_contents.__name__
Out[16]: 'f'

I don't think I would want to rely on this in production code though...

Kent


More information about the Tutor mailing list