Python metaclasses: Why isn't __setattr__ called for attributes set during class definition?
我有以下python代码:
1 2 3 4 5 6 7 8 9 10 | 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 |
我本以为
这种行为的原因是什么?有没有一种方法可以截获在创建课程期间发生的作业?在
类块大致是构建字典的语法甜头,然后调用元类来构建类对象。
这是:
1 2 3 4 5 | class Foo(object): __metaclass__ = FooMeta FOO = 123 def a(self): pass |
就像你写的:
1 2 3 4 5 6 7 | d = {} d['__metaclass__'] = FooMeta d['FOO'] = 123 def a(self): pass d['a'] = a Foo = d.get('__metaclass__', type)('Foo', (object,), d) |
只有在没有名称空间污染的情况下(实际上,还需要搜索所有的基来确定元类,或者是否存在元类冲突,但这里我忽略了这一点)。
元类"EDOCX1"(0)可以控制当您试图在它的一个实例(类对象)上设置属性时会发生什么,但在您不这样做的类块中,您正在插入字典对象,因此
除非你用的是python 3.x!在python 3.x中,可以在元类上定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | 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 |
运行这个可以让我:
1 2 3 4 5 6 7 8 | 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 |
一些注释:
遗憾的是,这种技术在python 2.x中不可用(我检查过)。没有调用
比较python 2:
1 2 3 4 5 | class Foo(object): __metaclass__ = FooMeta FOO = 123 def a(self): pass |
大致相当于:
1 2 3 4 5 6 7 | d = {} d['__metaclass__'] = FooMeta d['FOO'] = 123 def a(self): pass d['a'] = a Foo = d.get('__metaclass__', type)('Foo', (object,), d) |
其中要调用的元类是由字典确定的,而不是由python 3确定的:
1 2 3 4 | class Foo(metaclass=FooMeta): FOO = 123 def a(self): pass |
大致相当于:
1 2 3 4 5 6 | d = FooMeta.__prepare__('Foo', ()) d['Foo'] = 123 def a(self): pass d['a'] = a Foo = FooMeta('Foo', (), d) |
要使用的字典是从元类确定的。
在创建课程的过程中没有作业发生。或者:他们正在发生,但不是在你认为的情况下发生的。所有类属性都是从类主体范围中收集的,并作为最后一个参数传递给元类'
1 2 3 4 5 6 7 8 | 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中也不能有重复的键,因此在类创建期间,属性已经消除了重复。只有在类定义之后设置属性,才能触发自定义的setattr。
因为名称空间是一个dict,所以无法像其他问题建议的那样检查重复的方法。唯一可行的方法是解析源代码。