最近,我使用元类来实现单例。然后,我试图了解__metaclass__
这些东西是如何工作的。为此,我编写了以下代码:
import numpy as np
class Meta(type):
_instance = None
def __init__(cls, *args, **kwargs):
print('meta: init')
#return super(Meta, cls).__init__(*args, **kwargs)
def __new__(cls, *args, **kwargs):
print('meta: new')
return super(Meta, cls).__new__(cls, *args, **kwargs)
def __call__(cls, *args, **kwargs):
print('meta: call')
if cls._instance is None:
cls._instance = super(Meta, cls).__call__(*args, **kwargs)
print(cls._instance.__class__)
return cls._instance
class ClassA():
__metaclass__ = Meta
def __init__(self, *args, **kwargs):
self.val = np.random.randint(1000)
print('classA: init')
def __new__(cls, *args, **kwargs):
print('classA: new')
return super(ClassA, cls).__new__(cls, *args, **kwargs)
def __call__(cls, *args, **kwargs):
print('classA: call')
return super(ClassA, cls).__call__(*args, **kwargs)
class ClassB():
__metaclass__ = Meta
def __init__(self, *args, **kwargs):
print('classB: init')
self.val = np.random.randint(1000)
def __new__(cls, *args, **kwargs):
print('classB: new')
return super(ClassB, cls).__new__(cls, *args, **kwargs)
def __call__(cls, *args, **kwargs):
print('classB: call')
return super(ClassB, cls).__call__(*args, **kwargs)
class ClassC(ClassB):
def __init__(self, *args, **kwargs):
print('classC: init')
super(ClassC, self).__init__(self, *args, **kwargs)
self.test = 3
def __new__(cls, *args, **kwargs):
print('classC: new')
return super(ClassC, cls).__new__(cls, *args, **kwargs)
def __call__(cls, *args, **kwargs):
print('classC: call')
return super(ClassC, cls).__call__(*args, **kwargs)
if __name__ == '__main__':
a1 = ClassA()
b1 = ClassB()
a2 = ClassA()
b2 = ClassB()
c1 = ClassC()
print(a1.val)
print(b1.val)
print(a2.val)
print(b2.val)
我对此有两个问题:
为什么
cls._instance
是__main__.ClassA object
(或__main__.ClassA object
)?它不是由super
创建的父实例吗?为什么在创建
__call__
实例时只调用__init__
(因为它继承自该实例),为什么在ClassC
中什么都没有调用(既不ClassC
也不ClassB.__call__
)? 最佳答案
首先,这是代码的精简版本,其中一些“噪声”已删除,以提高可读性。
import random
class Meta(type):
_instance = None
def __call__(cls, *args, **kwargs):
print('meta: call: %s' % cls)
if cls._instance is None:
cls._instance = super(Meta, cls).__call__(*args, **kwargs)
print("meta: call: returning %s@%s" % (cls._instance.__class__, id(cls._instance)))
return cls._instance
class ClassA(object):
__metaclass__ = Meta
def __new__(cls, *args, **kwargs):
print('classA: new')
return super(ClassA, cls).__new__(cls, *args, **kwargs)
def __init__(self, *args, **kwargs):
self.val = random.randint(1, 1000)
print('classA: init')
class ClassB(object):
__metaclass__ = Meta
def __new__(cls, *args, **kwargs):
print('classB: new')
return super(ClassB, cls).__new__(cls, *args, **kwargs)
def __init__(self, *args, **kwargs):
print('classB: init')
self.val = random.randint(1, 1000)
class ClassC(ClassB):
def __new__(cls, *args, **kwargs):
print('classC: new')
return super(ClassC, cls).__new__(cls, *args, **kwargs)
def __init__(self, *args, **kwargs):
print('classC: init')
super(ClassC, self).__init__(self, *args, **kwargs)
self.test = 3
def main():
a1 = ClassA()
b1 = ClassB()
a2 = ClassA()
b2 = ClassB()
c1 = ClassC()
print(a1.val)
print(b1.val)
print(a2.val)
print(b2.val)
if __name__ == '__main__':
main()
现在您的问题:
为什么cls._instance是main.ClassA对象(或main.ClassA对象)?它不是由super创建的父实例吗?
没有为什么 ?对于您的情况,在调用
ClassA()
时,实际上是在调用Meta.__call__(ClassA)
。 super(Meta, cls)
将选择Meta.__mro__
中的下一个类,即type
,因此调用type.__call__(Meta, ClassA)
。 type.__call__(Meta, ClassA)
将依次调用ClassA.__new__()
和ClassA.__init__()
。因此,当然(希望,否则您将几乎无法使用继承),您确实会得到一个ClassA
实例。通常的规则是:
super(CurrentClass, obj_or_cls).something()
将在“父”类中选择something
实现(实际上是在__mro__
的下一个类中),但是self
(或cls
或其他)第一个参数仍然会指向self
(或cls
等)。 IOW,您正在选择方法的父实现,但是此方法仍将获取当前对象/类作为第一个参数。为什么在创建ClassC实例时只调用
__call__
(因为它继承自它),却在ClassC中什么都没有调用(既不是__init__
也不是ClassB.__call__
)不会调用
ClassC.__call__()
,因为您的代码中没有任何内容可以调用它。是ClassC.__metaclass__.__call__()
负责ClassC
实例化。您需要调用ClassC
的实例才能调用ClassC.__call__()
。不会调用
ClassC.__init__()
,因为实际上没有创建ClassC
的实例。原因是您首先创建ClassB
的实例。此时(ClassB
的第一个实例),ClassB
没有属性_instance
,因此可以在其父类object
上查找该属性。由于object
也没有属性_instance
,因此在ClassB.__class__
(即Meta
)上继续查找。这确实具有一个_instance
属性,即None
,因此您确实创建了一个ClassB
实例并将其绑定到ClassB._instance
(在ClassB.__dict__
中创建该属性)。然后,当您尝试实例化
ClassC
时,if cls._instance is None
测试将首先在_instance
上查找ClassC
。此时,ClassC
没有_instance
属性,因此在ClassC
第一父级(mro中的下一个类)ClassB
上进行了查找。由于您已经实例化了ClassB
一次,因此查找在此处结束,将ClassC._instance
解析为ClassB.__dict__["_instance"]
,所以您得到的是已经创建的ClassB
实例。如果您想要一个可行的实现,则必须摆脱
Meta._instance
并在_instance
中的每个类上将None
设置为Meta.__init__()
:class Meta(type):
def __init__(cls, *args, **kw):
cls._instance = None
def __call__(cls, *args, **kwargs):
print('meta: call: %s' % cls)
if cls._instance is None:
cls._instance = super(Meta, cls).__call__(*args, **kwargs)
print("meta: call: returning %s@%s" % (cls._instance.__class__, id(cls._instance)))
return cls._instance
或将
Meta.__call__()
中的测试替换为getattr()
:class Meta(type):
def __call__(cls, *args, **kwargs):
print('meta: call: %s' % cls)
if not getattr(cls, "_instance", None):
cls._instance = super(Meta, cls).__call__(*args, **kwargs)
print("meta: call: returning %s@%s" % (cls._instance.__class__, id(cls._instance)))
return cls._instance