元类、setattr和属性在Python和怪异行为中的使用

元类、setattr和属性在Python和怪异行为中的使用,python,closures,Python,Closures,我编写了一个元类,它定义了使用它的类的运行时属性。它只是从REQUIRED_KEYS属性中获取这些属性,然后声明一个属性,例如: class Base(object): REQUIRED_KEYS = () class __metaclass__(type): def __init__(cls, name, bases, nmspc): type.__init__(cls, name, bases, nmspc)

我编写了一个
元类
,它定义了使用它的类的运行时属性。它只是从REQUIRED_KEYS属性中获取这些属性,然后声明一个
属性
,例如:

class Base(object):
    REQUIRED_KEYS = ()

    class __metaclass__(type):
        def __init__(cls, name, bases, nmspc):

            type.__init__(cls, name, bases, nmspc)
            for attr in cls.REQUIRED_KEYS:
                setattr(cls, attr, property(lambda self: self._dict.get(attr, None)))

    def __init__(self, **kwargs):
        self._dict = dict(**kwargs)


class Config(Base):
    REQUIRED_KEYS = ('foo', 'bar')
如果未指定一个键,则在运行时中定义的属性将返回一个
None
值。我希望以下执行的行为正确,返回
1
None

config = Config(**{'foo': 1})
config.foo
config.bar
但是对于
foo
属性,它返回None,其中正确的预期值为
1

如果我通过一个闭包修改
属性
函数使用的
lambda
,如下面的代码片段所示,它可以正常工作

class __metaclass__(type):
    def __init__(cls, name, bases, nmspc):

        def get(attr):
            def _get(self):
                return self._dict.get(attr, None)
            return _get

        type.__init__(cls, name, bases, nmspc)
        for attr in cls.REQUIRED_KEYS:
            setattr(cls, attr, property(get(attr)))
在执行第一个代码段之后,我意识到lambda函数总是使用最后一个
attr
值调用。这意味着,当我们试图访问
foo
属性时,lambda使用
bar


有人知道这里发生了什么吗?

这是python中闭包的工作方式

attr
的值不存储在lambda中,只是对变量的引用

在代码中,所有lambda都包含对
attr
变量的相同引用,当调用lambda时,它将包含其最后一个值

可以使用默认参数强制复制该值:

setattr(cls, attr, property(lambda self, attr=attr: self._dict.get(attr, None)))
                                         ^^^^^^^^^

这就是闭包在python中的工作方式

attr
的值不存储在lambda中,只是对变量的引用

在代码中,所有lambda都包含对
attr
变量的相同引用,当调用lambda时,它将包含其最后一个值

可以使用默认参数强制复制该值:

setattr(cls, attr, property(lambda self, attr=attr: self._dict.get(attr, None)))
                                         ^^^^^^^^^

根据,如果名称在代码块中使用,但未绑定到代码块(且未声明为全局),则该使用将被视为对最近封闭函数的引用。(我的)。名称
attr
未在lambda getter中声明,因此它是元类
\uuuu init\uuuu
函数中变量
attr
引用,该函数始终指向最后一个属性。另外,
dict(**kwargs)
(在
Base
class init函数中)是不必要的,因为
kwargs
已经是一个字典(您可以键入
self.\u dict=kwargs
),在您的示例中,您可以将类初始化为
Config(foo=1)
,而不是
Config(**{foo':1}),如果在代码块中使用了名称,但该名称未绑定到代码块(并且未声明为全局),则该名称的使用将被视为对最近封闭函数的引用。(我的)。名称
attr
未在lambda getter中声明,因此它是元类
\uuuu init\uuuu
函数中变量
attr
引用,该函数始终指向最后一个属性。此外,
dict(**kwargs)
(在
Base
class init函数中)是不必要的,因为
kwargs
已经是一个字典(您可以键入
self.\u dict=kwargs
),在您的示例中,您可以将类初始化为
Config(foo=1)
,而不是
Config(**{foo':1})
,谢谢,我在这里没有意识到这个问题。谢谢,我在这里没有意识到这个问题。关于第二个问题,用一个隐含的句子从一个kwars或一个显式的句子来建立一个字典只是做同样的事情的不同方式,但不是一个问题。关于最后一条注释,
Config(**{'foo':'1'})
实际上来自类似
d={'foo':1};Config(**d)
只是在我的评论中放松了一下,让它更具可读性。关于第二个问题,给出一个隐式句子来从kwars或显式语句构建字典只是做同样事情的不同方法,但不是问题。关于最后一条注释,
Config(**{'foo':'1'})
实际上来自类似
d={'foo':1};Config(**d)
只是在我的注释中放松了,使其可读。