Python中的 метaclasses:几个澄清问题
在研究元类时,我开始深入了解Python中的元编程,但我有几个问题,在现有的文档中似乎没有明确的答案。
- 在元类中使用
__new__
和__init__
时,它们的参数必须定义得一样吗? - 在元类中定义类的
__init__
最有效的方法是什么? - 在元类中,有没有办法引用类实例(通常是self)?
2 个回答
在元类中同时使用
__new__
和__init__
时,它们的参数必须定义得一样吗?我觉得 Alex Martelli 说得很简洁:
class Name(Base1,Base2): <<body>> __metaclass__==suitable_metaclass
这意味着
Name = suitable_metaclass('Name', (Base1,Base2), <<dict-built-by-body>>)
所以暂时不要把 suitable_metaclass 当成元类来看,而是把它当成一个普通的类。每当你看到
suitable_metaclass('Name', (Base1,Base2), <<dict-built-by-body>>)
这就告诉你,suitable_metaclass 的
__new__
方法的参数应该类似于def __new__(metacls, name, bases, dct)
而
__init__
方法应该像这样def __init__(cls, name, bases, dct)
所以它们的参数并不是完全一样,但只是在第一个参数上有所不同。
在元类中定义
__init__
方法的最有效方式是什么?你说的有效是什么意思?其实如果你不想的话,根本不需要定义
__init__
。在元类中有没有办法引用类实例(通常是 self)?
没有,而且你也不应该需要这样做。任何依赖于类实例的东西应该在类定义中处理,而不是在元类中。
对于第一个问题:任何类的 __init__
和 __new__
方法都必须接受相同的参数,因为它们会用相同的参数被调用。通常情况下,__new__
方法会接受一些它会忽略的额外参数(比如 object.__new__
方法可以接受任何参数,但会忽略它们),这样在继承的时候就不需要重写 __new__
方法。不过,通常只有在你根本没有 __new__
方法的时候才会这样做。
在这里并没有问题,因为正如所说的,元类总是用相同的一组参数被调用,所以在参数方面你不会遇到麻烦。但是如果你要修改传给父类的参数,那么你需要在两个地方都进行修改。
对于第二个问题:通常你不会在元类中定义类的 __init__
方法。你可以写一个包装器,在元类的 __new__
或 __init__
中替换类的 __init__
方法,或者你可以在元类中重新定义 __call__
方法。如果你使用继承,前者的表现会很奇怪。
import functools
class A(type):
def __call__(cls, *args, **kwargs):
r = super(A, cls).__call__(*args, **kwargs)
print "%s was instantiated" % (cls.__name__, )
print "the new instance is %r" % (r, )
return r
class B(type):
def __init__(cls, name, bases, dct):
super(B, cls).__init__(name, bases, dct)
if '__init__' not in dct:
return
old_init = dct['__init__']
@functools.wraps(old_init)
def __init__(self, *args, **kwargs):
old_init(self, *args, **kwargs)
print "%s (%s) was instantiated" % (type(self).__name__, cls.__name__)
print "the new instance is %r" % (self, )
cls.__init__ = __init__
class T1:
__metaclass__ = A
class T2:
__metaclass__ = B
def __init__(self):
pass
class T3(T2):
def __init__(self):
super(T3, self).__init__()
这是调用它的结果:
>>> T1()
T1 was instantiated
the new instance is <__main__.T1 object at 0x7f502c104290>
<__main__.T1 object at 0x7f502c104290>
>>> T2()
T2 (T2) was instantiated
the new instance is <__main__.T2 object at 0x7f502c0f7ed0>
<__main__.T2 object at 0x7f502c0f7ed0>
>>> T3()
T3 (T2) was instantiated
the new instance is <__main__.T3 object at 0x7f502c104290>
T3 (T3) was instantiated
the new instance is <__main__.T3 object at 0x7f502c104290>
<__main__.T3 object at 0x7f502c104290>
对于第三个问题:是的,正如上面所示,来自 __call__
。