最近,我使用元类来实现单例。然后,我试图了解__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

10-08 05:02