利用元 class 在 python 中创建单例 class

Create singleton class in python by taking advantage of meta class

我在网上看到了一些关于如何利用 python metaclass 强制将 class 创建为 Singleton 的资料。代码片段大致如下:

class SingletonMetaClass(type):
    _instance = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instance:
            # usually a lock is used here, but omitted for simplicity
            cls._instance[cls] = super().__call__(*args, **kwargs)

        return cls._instance[cls]

class SingletonBaseClass(object, metaclass=SingletonMetaClass):
    def __init__():
        pass

class SingletonDerivedClass(SingletonBaseClass):
    def __init__():
        pass

上面的代码片段非常适合我 - SingletonDerivedClass 的所有实例都是相同的。然而,我发现奇怪的是 SingletonMetaClass 中的那一行 cls._instance[cls] = super().__call__(*args, **kwargs)。显然,前者 cls 指的是 SingletonMetaClass 本身,而后者指的是子 class,在这种情况下,它将是 SingletonDerivedClass,但内部 __call__的签名只有一个cls,python解释器如何判断哪个cls指的是什么?

如有任何回复,我们将不胜感激!

None 的 cls 用法是指 metaclass。它们指的是 metaclass 的实例,SingletonBaseClassSingletonDerivedClass.

clsSingletonBaseClass 时查找 cls._instance 找到 SingletonMetaClass._instance 因为 SingletonBaseClassSingletonMetaClass 的实例,并查找属性在对象上也会搜索其 class,即使对象本身也是 class.

“利用元 class 在 python 中创建单例 class”

不要。

请。

这定义了矫枉过正。

所以,在解释哪里出了问题或你的疑问之前,这里是你如何在 Python 中定义一个单例,它只能作为一个单例使用,除非有人决定使用内省来“创建更多而不是一个单身人士”- 在这一点上,这个人也可以滥用你在那里拥有的任何机制。

只需创建一个 class,然后创建将成为您的单例的实例。然后从命名空间中删除对 class 的引用。 如果有代码会尝试实例化应该是单例的 class,请放置一个返回 self:

__call__ 方法
class MySingleton():
    ...
    def __call__(self):
        return self

mysingleton = MySingleton()
del MySingleton

如果代码不希望实例化 class,或者您出于其他原因需要单例可调用:此 __call__ 只是装饰性的。实际上,单例实例可以与 class 同名,甚至不需要 del 语句。

现在,对于您在代码中感到困惑的部分:就元 class 中的任何方法而言,如果它是一个将是“实例”方法的方法:即采用“self” " 作为第一个参数,第一个参数指的是 class! (不是元class)。所以,我们在写metaclass.

的方法时,通常会写cls作为参数名

换句话说,在您的 __call__ 情况下,“cls”是元 class 的实例:classes 本身。在 __call__ 中为参数使用名称 cls 并不能神奇地使其接收到对写入方法的 class 的引用(metaclass 本身)。

如果给定您的 class,您想要获得对元 class 的引用,只需像处理任何其他 Python 对象一样继续操作:使用 type 调用或检查 __class__ 属性。

因此,如果您选择忽略我的建议,让元classes 只是为了拥有一个单例,那么您的代码的修复方法是:

def __call__(cls, *args, **kwargs):
    if cls not in cls._instance:
        # usually a lock is used here, but omitted for simplicity
        cls.__class__._instance[cls] = super().__call__(*args, **kwargs)

    return cls._instance[cls]

如果你想让 metaclass 方法中的第一个参数成为 metaclass 本身,@classmethod 装饰器会这样做(但这样的修饰符只能应用于普通方法 - 除了“dunder”特殊方法,如 __call__ - 语言期望 __call__ 和其他人将实例作为第一个参数)