Python元类:为什么不';在类定义过程中,是否为属性集调用了t__setattr__?

Python元类:为什么不';在类定义过程中,是否为属性集调用了t__setattr__?,python,metaclass,Python,Metaclass,我有以下python代码: class FooMeta(type): def __setattr__(self, name, value): print name, value return super(FooMeta, self).__setattr__(name, value) class Foo(object): __metaclass__ = FooMeta FOO = 123 def a(self): p

我有以下python代码:

class FooMeta(type):
    def __setattr__(self, name, value):
        print name, value
        return super(FooMeta, self).__setattr__(name, value)

class Foo(object):
    __metaclass__ = FooMeta
    FOO = 123
    def a(self):
        pass
我希望
\uuu setattr\uuuu
FOO
a
调用元类。然而,它根本不被称为。当我给
Foo.whatever赋值时,在类被定义之后,方法被调用


这种行为的原因是什么?有没有办法截获在创建类的过程中发生的赋值?在
\uuu new\uuuu
中使用
attrs
将不起作用,因为我想这样做。

类属性作为一个单独的字典传递给元类,我的假设是,它用于一次更新类的
\uu dict\uuuu
属性,例如
cls.\uu dict\uuu.update(dct)之类的东西
而不是对每个项目执行
setattr()
。更重要的是,它都是在C-land中处理的,根本不是为了调用自定义的
\uuuu setattr\uuuu()


在元类的
\uuuu init\uuuu()
方法中,可以很容易地对类的属性执行任何操作,因为类名称空间是作为
dict
传递的,所以只需执行该操作。

在创建类的过程中不会发生赋值。或者:它们正在发生,但不是在你认为它们正在发生的环境中。所有类属性都从类主体范围收集,并作为最后一个参数传递给元类“
\uuuu new\uuuu

class FooMeta(type):
    def __new__(self, name, bases, attrs):
        print attrs
        return type.__new__(self, name, bases, attrs)

class Foo(object):
    __metaclass__ = FooMeta
    FOO = 123

原因:当类主体中的代码执行时,还没有类。这意味着元类还没有机会截取任何内容。

在类创建过程中,您的名称空间将被计算为dict,并与类名和基类一起作为参数传递给元类。正因为如此,在类定义中指定类属性不会按预期的方式工作。它不会创建一个空类并分配所有内容。dict中也不能有重复的键,因此在类创建过程中,属性已经被重复消除。只有在类定义之后设置属性,才能触发自定义设置属性


因为名称空间是一个dict,所以您无法检查重复的方法,正如您的另一个问题所建议的那样。唯一实用的方法是解析源代码。

类块大致上是用于构建字典的语法糖,然后调用元类来构建类对象

这:

看起来就像你写的:

d = {}
d['__metaclass__'] = FooMeta
d['FOO'] = 123
def a(self):
    pass
d['a'] = a
Foo = d.get('__metaclass__', type)('Foo', (object,), d)
只有在没有名称空间污染的情况下(实际上,还需要搜索所有的基来确定元类,或者是否存在元类冲突,但我在这里忽略了这一点)

元类“
\uuuu setattr\uuuu
可以控制当您试图在它的一个实例(类对象)上设置属性时会发生什么,但是在类块内您没有这样做,而是插入到字典对象中,因此
dict
类控制正在发生的事情,而不是元类。所以你运气不好


除非您使用的是Python 3.x!在Python3.x中,您可以在元类上定义一个
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
类方法(或staticmethod),它控制在类块中的属性集传递给元类构造函数之前,使用哪个对象来累积这些属性集。默认的
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

from collections import MutableMapping


class SingleAssignDict(MutableMapping):
    def __init__(self, *args, **kwargs):
        self._d = dict(*args, **kwargs)

    def __getitem__(self, key):
        return self._d[key]

    def __setitem__(self, key, value):
        if key in self._d:
            raise ValueError(
                'Key {!r} already exists in SingleAssignDict'.format(key)
            )
        else:
            self._d[key] = value

    def __delitem__(self, key):
        del self._d[key]

    def __iter__(self):
        return iter(self._d)

    def __len__(self):
        return len(self._d)

    def __contains__(self, key):
        return key in self._d

    def __repr__(self):
        return '{}({!r})'.format(type(self).__name__, self._d)


class RedefBlocker(type):
    @classmethod
    def __prepare__(metacls, name, bases, **kwargs):
        return SingleAssignDict()

    def __new__(metacls, name, bases, sad):
        return super().__new__(metacls, name, bases, dict(sad))


class Okay(metaclass=RedefBlocker):
    a = 1
    b = 2


class Boom(metaclass=RedefBlocker):
    a = 1
    b = 2
    a = 3
运行此命令可以让我:

Traceback (most recent call last):
  File "/tmp/redef.py", line 50, in <module>
    class Boom(metaclass=RedefBlocker):
  File "/tmp/redef.py", line 53, in Boom
    a = 3
  File "/tmp/redef.py", line 15, in __setitem__
    'Key {!r} already exists in SingleAssignDict'.format(key)
ValueError: Key 'a' already exists in SingleAssignDict
大致相当于:

d = {}
d['__metaclass__'] = FooMeta
d['FOO'] = 123
def a(self):
    pass
d['a'] = a
Foo = d.get('__metaclass__', type)('Foo', (object,), d)
d = FooMeta.__prepare__('Foo', ())
d['Foo'] = 123
def a(self):
    pass
d['a'] = a
Foo = FooMeta('Foo', (), d)
其中,要调用的元类由字典决定,而不是Python 3:

class Foo(metaclass=FooMeta):
    FOO = 123
    def a(self):
        pass
大致相当于:

d = {}
d['__metaclass__'] = FooMeta
d['FOO'] = 123
def a(self):
    pass
d['a'] = a
Foo = d.get('__metaclass__', type)('Foo', (object,), d)
d = FooMeta.__prepare__('Foo', ())
d['Foo'] = 123
def a(self):
    pass
d['a'] = a
Foo = FooMeta('Foo', (), d)

要使用的字典的位置由元类决定。

正是这个问题导致了py3中元类语法的改变!