【问题标题】:Create singleton class in python by taking advantage of meta class利用元类在python中创建单例类
【发布时间】:2021-08-02 10:28:38
【问题描述】:

我在网上看到了一些关于如何利用 python 元类强制将类创建为单例的材料。代码sn-p大致如下:

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

上面的 sn-p 非常适合我 - SingletonDerivedClass 的所有实例都是相同的。然而,我发现奇怪的是SingletonMetaClass 中的那行cls._instance[cls] = super().__call__(*args, **kwargs)。显然,前者cls指的是SingletonMetaClass本身,后者指的是子类,在这种情况下,它将是SingletonDerivedClass,但是__call__的签名里面只有一个cls,怎么办python解释器告诉哪个cls指的是什么?

任何回复将不胜感激!

【问题讨论】:

    标签: python python-3.x metaclass


    【解决方案1】:

    cls 的所有用法都不是指元类。它们引用元类的实例,SingletonBaseClassSingletonDerivedClass

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

    【讨论】:

      【解决方案2】:

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

      不要。

      请。

      这定义了矫枉过正。

      所以,在解释出了什么问题或你的疑问之前,这里是你如何在 Python 中定义一个单例,它只能用作单例,除非有人决定使用自省来“创建多个单例”——在这一点,这个人也可以滥用你那里的任何机制。

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

      class MySingleton():
          ...
          def __call__(self):
              return self
      
      mysingleton = MySingleton()
      del MySingleton
      
      

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

      现在,对于您在代码中感到困惑的部分:就元类中的任何方法而言,如果它是一个“实例”方法的方法:即以“self”作为第一个参数,那第一个参数指的是一个类! (不是元类)。所以,我们在编写元类的方法时,通常会写cls作为参数名。

      换句话说,对于您的__call__,“cls”是元类的实例:类本身。为__call__ 中的参数使用名称cls 并不会神奇地使其接收到编写方法的类(元类本身)的引用。

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

      因此,如果您选择忽略我的建议,只为拥有一个单例而使用元类,那么您的代码的解决方法是:

      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]
      

      如果您希望元类方法中的第一个参数是元类本身,@classmethod 装饰器可以做到这一点(但这种修饰符只能应用于普通方法 - 除了“dunder”特殊方法,如 __call__ - 该语言期望__call__ 和其他人将实例作为第一个参数)

      【讨论】:

        猜你喜欢
        • 2014-03-01
        • 1970-01-01
        • 2015-07-17
        • 1970-01-01
        • 2017-11-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多