Python3奇怪的元类行为

Python3奇怪的元类行为,python,python-3.x,metaclass,Python,Python 3.x,Metaclass,我在Python 3中玩元类: class M(type): def __new__(cls, clsname, bases, attrs): for name, attr in attrs.items(): if callable(attr): attrs[name] = attr return type.__new__(cls, clsname, bases, attrs) class C(me

我在Python 3中玩元类:

class M(type):
    def __new__(cls, clsname, bases, attrs):
        for name, attr in attrs.items():
            if callable(attr):
                attrs[name] = attr
        return type.__new__(cls, clsname, bases, attrs)

class C(metaclass=M):
    def f(self, x):
        print(x)

if __name__ == '__main__':
    c = C()
    c.f(1)
    c.f(2)
到目前为止没有什么特别的,我只是连接到一个类的创建中,并用。。。好吧,它本身,所以难怪一切都正常。但是:

class M(type):
    def __new__(cls, clsname, bases, attrs):
        for name, func in attrs.items():
            if callable(func):
                attrs[name] = lambda *args, **kwds: func(*args, **kwds)
        return type.__new__(cls, clsname, bases, attrs)
它有时有效,有时无效:

user$ python test.py
1
2
user$ python test.py
Traceback (most recent call last):
  File "./meta.py", line 23, in <module>
    main()
  File "./meta.py", line 19, in main
    instance.method(1)
  File "./meta.py", line 9, in <lambda>
    attrs[name] = lambda *args, **kwds: func(*args, **kwds)
TypeError: 'str' object is not callable
user$python test.py
1.
2.
用户$python test.py
回溯(最近一次呼叫最后一次):
文件“/meta.py”,第23行,在
main()
文件“/meta.py”,第19行,主
实例.方法(1)
文件“/meta.py”,第9行,在
attrs[name]=lambda*args,**kwds:func(*args,**kwds)
TypeError:“str”对象不可调用
但我只是用一个lambda包装器替换了它的方法!“str”和什么有什么关系?我做错了什么

(以防万一,这是一个奇怪的依赖于平台的实现问题,我正在使用Ubuntu服务器12.04.3…)


更新:修复了回溯中的名称不匹配问题。

进一步阐述我的评论:

def makelambda(func):
    return lambda *args, **kwds: func(*args, **kwds)

class M(type):
    def __new__(cls, clsname, bases, attrs):
        for name, func in attrs.items():
            if callable(func):
                attrs[name] = makelambda(func)
        return type.__new__(cls, clsname, bases, attrs)
这是必要的,因为在原始代码中,在lambda内部,
func
指的是
func
\uuuuuuuuu新方法返回时所具有的任何值,而不是创建lambda时所具有的值。这是违反直觉的,但您可以验证它:

lambdas = [lambda: x for x in range(10)]
print(lambdas[0]())    # prints 9, as do lambdas[1] through [9]
为了解决这个问题,我们使用一个单独的函数来创建lambda,从而在创建lambda时“冻结”
func
变量的值。您也可以使用lambda上的默认参数值来执行此操作,但是由于您在这里使用的是
*
**
,因此这有点问题


(该行为与元类无关,您可以在定义lambda并在创建它们后更改其中使用的变量值的任何地方看到相同的行为。lambda在这方面与任何其他函数都没有区别。)

您的回溯与代码不匹配<代码>属性
!=<代码>函数,
属性!=attrs
。在本例中,
属性
似乎是一个字符串对象,因此,
可调用的
测试似乎缺失。此外,您的下一个问题:
func
不会指向您认为它指向的lambda内部。(它将引用您在循环中看到的最后一个属性,该属性可能无法调用。)要解决此问题,请编写一个帮助函数来创建lambda,传入该函数,而不是在您的循环中创建它。可能重复@kindall:bingo,这就是这里的问题。