Python元类:为什么不';在类定义过程中,是否为属性集调用了t__setattr__?
我有以下python代码: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
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中元类语法的改变!