python scope issue with anonymous lambda in metaclass -
i using metaclass define read-only properties (accessor methods) class, adding property getter (a lambda) each field declared class. finding different behavior depending on define lambda. works if define getter lambda in external function called __new__ method of metaclass, , not if define lambda in directly in __new__ method of metaclass.
def _getter(key): meth = lambda self : self.__dict__[key] print "_getter: created lambda %s key %s" % (meth, key) return meth class readonlyaccessors(type): def __new__(cls, clsname, bases, dict): fname in dict.get('_fields',[]): key = "_%s" % fname # way works dict[fname] = property(_getter(key)) # way doesn't # meth = lambda self : self.__dict__[key] # print "readonlyaccessors.__new__: created lambda %s key %s" % (meth, key) # dict[fname] = property(meth) return type.__new__(cls, clsname, bases, dict) class rothingy(object): __metaclass__ = readonlyaccessors _fields = ("name", "number") def __init__(self, **initializers): fname in self._fields: self.__dict__[ "_%s" % fname ] = initializers.get(fname, none) print self.__dict__ if __name__ == "__main__": rot = rothingy(name="fred", number=100) print "name = %s\nnumber = %d\n" % (rot.name, rot.number) as written, execution looks this:
[slass@zax src]$ python readonlyaccessors.py _getter: created lambda <function <lambda> @ 0x7f652a4d88c0> key _name _getter: created lambda <function <lambda> @ 0x7f652a4d8a28> key _number {'_number': 100, '_name': 'fred'} name = fred number = 100 commenting out line follows "the way works" , uncommenting 3 lines following "the way doesn't" produces this:
[slass@zax src]$ python readonlyaccessors.py readonlyaccessors.__new__: created lambda <function <lambda> @ 0x7f40f5db1938> key _name readonlyaccessors.__new__: created lambda <function <lambda> @ 0x7f40f5db1aa0> key _number {'_number': 100, '_name': 'fred'} name = 100 number = 100 note though rot.__dict__ shows _name 'fred', value returned name property 100.
clearly i'm not understanding scope in i'm creating lambdas.
i've been reading guido's document metaclass accessors here: https://www.python.org/download/releases/2.2.3/descrintro/#cooperation python docs python data model , http://code.activestate.com/recipes/307969-generating-getset-methods-using-a-metaclass/ recipe creating accessors using metaclass, , on stackoverflow can find, i'm not getting it.
thank you.
-mike
the problem has scope. when define meth with
meth = lambda self : self.__dict__[key] the key variable not variable in meth's local scope. when meth function called, key must searched in enclosing scope. (see legb rule.) finds in scope of __new__ method. however, time meth gets called, value of key not value of key when meth defined. rather value of key the last value assigned due for-loop. happens '_number'. no matter meth call, value of self.__dict__['_number'] being returned.
you can see happening defining meth way inside __new__:
fname in dict.get('_fields',[]): key = "_%s" % fname def meth(self): print(key) # see `meth` believes `key` return self.__dict__[key] yields
_number # key `_number` _number name = 100 number = 100 the reason why _getter works because key gets passed _getter. when meth gets called, finds value of key in _getter's scope, key retains value got when _getter called.
if want use lambda instead of _getter, can using default value key:
meth = lambda self, key=key: self.__dict__[key] now, inside meth, key local variable. when meth called, value of key value of key in local scope. default value bound function @ definition-time, right value being bound each meth lambda function.
Comments
Post a Comment