类块大致是构建字典的语法糖,然后调用元类来构建类对象。
这个:
class Foo(object):
__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)
只有没有命名空间污染(实际上,还需要搜索所有基础以确定元类,或者是否存在元类冲突,但我在这里忽略了这一点)。
元类'__setattr__
可以控制当您尝试在其实例之一(类对象)上设置属性时发生的情况,但在类块内您没有这样做,您插入到字典对象中,因此dict
类控制发生了什么,而不是你的元类。所以你运气不好。
除非您使用的是 Python 3.x!在 Python 3.x 中,您可以__prepare__
在元类上定义一个类方法(或静态方法),它控制在将类块中的属性集传递给元类构造函数之前,使用什么对象来累积属性集。默认__prepare__
只返回一个普通字典,但您可以构建一个不允许重新定义键的自定义类字典,并使用它来累积您的属性:
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
一些注意事项:
__prepare__
必须是classmethod
or staticmethod
,因为它是在元类的实例(您的类)存在之前被调用的。
type
仍然需要它的第三个参数是一个真实的dict
,所以你必须有一个__new__
将 转换SingleAssignDict
为普通参数的方法
- 我本可以
dict
对. 所以我更喜欢子类化和包装字典。update
__setitem__
collections.MutableMapping
- 实际的
Okay.__dict__
对象是一个普通的字典,因为它是由它设置的type
并且type
对它想要的字典类型很挑剔。这意味着在类创建之后覆盖类属性不会引发异常。如果要保持类对象字典强制的不覆盖,可以__dict__
在调用超类后覆盖属性。__new__
遗憾的是,这种技术在 Python 2.x 中不可用(我检查过)。该__prepare__
方法没有被调用,这很有意义,因为在 Python 2.x 中,元类是由__metaclass__
魔法属性而不是类块中的特殊关键字确定的;这意味着用于累积类块属性的 dict 对象在元类已知时已经存在。
比较 Python 2:
class Foo(object):
__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)
与 Python 3 相比,要调用的元类是从字典中确定的:
class Foo(metaclass=FooMeta):
FOO = 123
def a(self):
pass
大致相当于:
d = FooMeta.__prepare__('Foo', ())
d['Foo'] = 123
def a(self):
pass
d['a'] = a
Foo = FooMeta('Foo', (), d)
要使用的字典在哪里由元类确定。