我有以下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
我曾期望__setattr__meta类同时被称为FOO和a。但是,根本没有调用它。当我给你的东西Foo.whatever类已经被定义之后,该方法 被 调用。
__setattr__
FOO
a
Foo.whatever
这种行为的原因是什么,有没有办法拦截在创建类期间发生的作业?使用attrsin__new__无效,因为我想检查方法是否正在重新定义。
attrs
__new__
类块大致是语法糖,用于构建字典,然后调用元类来构建类对象。
这个:
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类控件这是怎么回事,而不是您的元类。所以你很不走运。
dict
除非您使用的是Python 3.x!在Python 3.x中,您可以__prepare__在元类上定义一个类方法(或静态方法),该方法控制在将对象集传递给元类构造函数之前,使用哪个对象累积在类块中设置的属性。默认值__prepare__只是返回一个普通的字典,但是您可以构建一个自定义类似dict的类,该类不允许重新定义键,并使用该类来累积您的属性:
__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
一些注意事项:
classmethod
staticmethod
type
SingleAssignDict
update
__setitem__
collections.MutableMapping
Okay.__dict__
__dict__
遗憾的是,该技术在Python 2.x中不可用(我检查过)。该__prepare__方法不会被调用,这在Python 2.x中是有意义的,元类是由__metaclass__magic属性而不是classblock中的特殊关键字确定的;这意味着在知道元类时,用于为类块累积属性的dict对象已经存在。
__metaclass__
比较Python 2:
大致相当于:
与字典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)
使用字典的位置由元类确定。