I am using a metaclass to define read-only properties (accessor methods) for a class, by adding a property with only a getter (a lambda) for each field declared by the class. I am finding different behavior depending on where I define the lambda. It works if I define the getter lambda in an external function that is called by the __new__
method of the metaclass, and not if I define the lambda in directly in the __new__
method of the metaclass.
def _getter(key):
meth = lambda self : self.__dict__[key]
print "_getter: created lambda %s for key %s" % (meth, key)
return meth
class ReadOnlyAccessors(type):
def __new__(cls, clsName, bases, dict):
for fname in dict.get('_fields',[]):
key = "_%s" % fname
# the way that works
dict[fname] = property(_getter(key))
# the way that doesn't
# meth = lambda self : self.__dict__[key]
# print "ReadOnlyAccessors.__new__: created lambda %s for 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):
for 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 currently written, execution looks like this:
[slass@zax src]$ python ReadOnlyAccessors.py
_getter: created lambda <function <lambda> at 0x7f652a4d88c0> for key _name
_getter: created lambda <function <lambda> at 0x7f652a4d8a28> for key _number
{'_number': 100, '_name': 'Fred'}
name = Fred
number = 100
Commenting out the line that follows "the way that works" and uncommenting the three lines following "the way that doesn't" produces this:
[slass@zax src]$ python ReadOnlyAccessors.py
ReadOnlyAccessors.__new__: created lambda <function <lambda> at 0x7f40f5db1938> for key _name
ReadOnlyAccessors.__new__: created lambda <function <lambda> at 0x7f40f5db1aa0> for key _number
{'_number': 100, '_name': 'Fred'}
name = 100
number = 100
Note that even though the rot.__dict__
shows that _name
is 'Fred'
, the value returned by the name
Property is 100
.
Clearly I'm not understanding something about the scope in which I'm creating the lambdas.
I've been reading Guido's document about metaclass for accessors here: https://www.python.org/download/releases/2.2.3/descrintro/#cooperation as well as the Python docs for the Python Data Model and this http://code.activestate.com/recipes/307969-generating-getset-methods-using-a-metaclass/ recipe for creating accessors using a metaclass, and finally everything on StackOverflow that I can find, but I'm just not getting it.
Thank you.
-Mike