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



