【问题标题】:How a metaclass singleton works元类单例如何工作
【发布时间】:2021-08-03 22:49:54
【问题描述】:

我不清楚the typical metaclass singleton implementation 是如何工作的。我希望加星标的打印执行两次;它只发生一次:

class _Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        print('Within __call__', cls, cls._instances, *args, **kwargs)
        if cls not in cls._instances:
            print('**About to call __call__', super(_Singleton, cls).__call__, flush=True)
            print("Is cls the '...of object'?", hex(id(cls)).upper())
            cls._instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs)
            print(cls._instances[cls])
        return cls._instances[cls]


class MySingleton(metaclass=_Singleton):
    pass


if __name__ == '__main__':
    print('Making mysingleton')
    mysingleton = MySingleton()
    print("Is mysingleton the 'cls'?", mysingleton)

    print('Making another')
    another = MySingleton()

    # Verify singletude
    assert another == mysingleton

打印出来

Making mysingleton
Within __call__ <class '__main__.MySingleton'> {}
**About to call __call__ <method-wrapper '__call__' of _Singleton object at 0x000001C950C28780>
Is cls the '...of object'? 0X1C950C28780
<__main__.MySingleton object at 0x000001C9513FCA30>
Is mysingleton the 'cls'? <__main__.MySingleton object at 0x000001C9513FCA30>
Making another
Within __call__ <class '__main__.MySingleton'> {<class '__main__.MySingleton'>: <__main__.MySingleton object at 0x000001C9513FCA30>}


根据我对 Python 文档的经验,它们非常循环和混乱。文档说 __call__() 在实例被“调用”时被调用。很公平;运行MySingleton.__call__ 是因为mysingleton = MySingleton() 上的“()”表示函数调用。 MySingleton 是 _Singleton 类型,所以它有一个 _instances 字典。 dict 在第一次调用时自然是空的。条件失败,Super(_Singleton, cls).__call__ 执行。

super() 在这里做什么?从文档中几乎无法理解。它返回一个“代理对象”,在别处解释为“一个'引用'共享对象的对象”,它将方法调用委托给'type'的父类或兄弟类。好的;它将用于调用一些相关的_Singleton类型的方法。

这里使用的 super() 的两个参数形式“准确地指定参数并进行适当的引用”。那些参考是什么?类型是_Singleton,对象是cls,不是mysingleton。这是0x000001C950C28780 的任何对象。无论如何,super() 的搜索顺序是getattr()super()。我认为这意味着根据_Singleton.__mro__ 查找引用,因为__call__ 不是属性(或者它是?)。也就是说,super() 调用根据 super() 查找,我假设它是 _Singleton。清如泥。 __mro__ 产生 (&lt;class '__main__._Singleton'&gt;, &lt;class 'type'&gt;, &lt;class 'object'&gt;)。因此,super(_Singleton, cls) 将寻找“相关的_Singleton 类型”并调用其__call__ 方法;我假设那是cls.__call__()

由于 cls 是 _Singleton,我希望看到第二个打印件。实际上,我期望某种递归。两者都不会发生。里面发生了什么?

【问题讨论】:

    标签: python singleton super


    【解决方案1】:

    super 内置函数并不是 Python 语法中最简单的东西。当方法在类层次结构中被覆盖时使用它,并允许间接指定实际调用哪个版本(在哪个祖先类中定义的方法)。

    这里,_Singletontype 的子类。很公平。但由于_Singleton__call__ 方法已被覆盖,它必须在其父类中调用相同的方法 才能真正构建对象。这就是super(_Singleton, cls).__call__(*args, **kwargs) 的目的:它将呼叫转发给_Singleton 的父级。所以它是一样的:

    type.__call__(cls, *args, **kwargs)
    

    即:它调用type__call__方法,但仍使用cls作为self对象,允许创建MySingleton对象并绕过递归调用_Singleton.__call__。替代方法是使用S.__new__(S, *args, **kwargs) 或直接使用object.__new__(S),但最后一个会绕过任何可能的对象初始化。

    实际上,super 是这里的 Pythonic 方式,因为如果您稍后构建更复杂的元类层次结构(_Singleton super(_Singleton, cls) 将确保使用紧接在 _Singleton 之前的类在层次结构中。

    【讨论】:

    • 这阐明了super() 解析的内容以及它如何避免递归。那么type.__call__ 究竟做了什么?手册上说“对类型没有特殊操作”。我看到type.__call__(cls, *args, **kwargs) 返回&lt;__main__.MySingleton object at 0x000001C9513FCA30&gt;。我注意到这与type(cls.__name__, (), {}) 的结果相同。 type 是否只是使用传递给它的任何对象的名称创建一个新类型?
    • 我看到type.__call__&lt;slot wrapper '__call__' of 'type' objects&gt;。这看起来像是用 C 编写的任何实现的包装器。
    • @LoremIpsum:当心。 type(cls.__name__, (), {}) 构建一个类,而 type.__call__(cls) 构建一个 cls 的实例。
    猜你喜欢
    • 2016-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-08
    • 1970-01-01
    • 2018-02-11
    相关资源
    最近更新 更多