我正在使用元类来为类定义只读属性(访问器方法),方法是为类声明的每个字段添加一个只有getter(lambda)的属性。我发现不同的行为取决于我定义lambda的位置。如果我在由元类的__new__
方法调用的外部函数中定义getter lambda,那么它就可以工作,而不是直接在元类的__new__
方法中定义lambda。
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)
如目前所述,执行如下:
[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
评论“工作方式”之后的行,并在“不通过的方式”之后取消注释三行:
[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
请注意,即使rot.__dict__
显示_name
为'Fred'
,name
媒体资源返回的值为100
。
显然,我不了解我正在创建lambdas的范围。
我一直在阅读Guido关于访问者元类的文档: https://www.python.org/download/releases/2.2.3/descrintro/#cooperation 以及Python数据模型和Python的Python文档 http://code.activestate.com/recipes/307969-generating-getset-methods-using-a-metaclass/ 使用元类创建访问器的方法, 最后我可以找到StackOverflow上的所有内容,但我只是没有得到它。
谢谢。
-Mike
答案 0 :(得分:2)
问题与scope有关。使用
定义meth
时
meth = lambda self : self.__dict__[key]
key
变量不是meth
本地范围内的变量。因此,在调用meth
函数时,必须在封闭范围内搜索key
。 (参见LEGB rule。)它在__new__
方法的范围内找到它。但是,在调用meth
时,key
的值不一定是定义key
时meth
的值。相反,key
的值是由于for-loop
而分配给它的最后一个值。它总是'_number'
。因此,无论您调用的是meth
,都会返回self.__dict__['_number']
的值。
您可以通过meth
内部__new__
来定义 for fname in dict.get('_fields',[]):
key = "_%s" % fname
def meth(self):
print(key) # See what `meth` believes `key` is
return self.__dict__[key]
来发现这种情况:
_number # key is always `_number`
_number
name = 100
number = 100
产量
_getter
key
有效的原因是_getter
传递给meth
。因此,当调用key
时,它会在_getter
的范围内找到key
的值,其中_getter
会保留_getter
时获得的值被叫了。
如果您想使用lambda而不是key
,可以使用meth = lambda self, key=key: self.__dict__[key]
的默认值:
meth
现在,在key
内,meth
是本地变量。因此,当调用key
时,key
的值将是本地范围中meth
的值。默认值绑定到 definition-time 处的函数,因此正确的值绑定到每个{{1}} lambda函数。
答案 1 :(得分:1)
这是python闭包的“后期绑定”的另一种表现形式,与元类无关;-) - 虽然可能是游戏中的元类使实际问题更难看...考虑:
funcs = [lambda: x for x in range(30)]
print funcs[0]() # 29!
原因是lambda函数在调用时从闭包中查找值,而不是在创建它时。在这种情况下,即使i
在创建第一个函数时为0
,在调用它时,i
的值为29
。
现在在你的情况下,你发生了同样的事情,只有变量key
。解决这个问题的一种简单方法是将值作为关键字参数绑定到函数中(在创建时进行评估):
funcs = [lambda _x=x: _x for x in range(30)]
或者,在您的情况下:
meth = lambda self, _key: self.__dict__[_key]
答案 2 :(得分:0)
关键词是动态范围。
这里很容易进入陷阱。
让问题更容易,忘掉OO,试着考虑下面的代码:
arr = []
for i in range(5):
arr.append(lambda: i)
for lmb in arr:
print lmb()
和这段代码:
def lmb_gen(val):
return lambda: val
arr = []
for i in range(5):
arr.append(lmb_gen(i))
for lmb in arr:
print lmb()
简单的回答是在lambda中的i被绑定到for循环中的i,它在lambda被调用之前一直在变化。这就是5 4打印的原因。
而在第二个例子中,lambda中的val绑定到参数val,每次调用lmb_gen时都会变化。换句话说,环境是不同的。
规则是,当一个变量未在函数中定义时,该变量实际上被绑定到第一个“外部”环境。
这种现象不仅发生在lambda的情况下,而且发生在命名函数的情况下。