这个问题不是为了讨论单例设计模式是否可取、是否是一种反模式,或者是否用于任何宗教战争,而是为了讨论如何以最Python化的方式在Python中最好地实现这种模式。在这个例子中,我定义“最蟒蛇”是指它遵循“最少惊讶的原则”。

我有多个类将成为单类(我的用例是一个记录器,但这并不重要)。当我可以简单地继承或装饰时,我不希望在几个类中添加口香糖。

最佳方法:


方法1:装饰器

def singleton(class_):
    instances = {}
    def getinstance(*args, **kwargs):
        if class_ not in instances:
            instances[class_] = class_(*args, **kwargs)
        return instances[class_]
    return getinstance

@singleton
class MyClass(BaseClass):
    pass

Pros

装饰符的添加方式通常比多重继承更直观。

Cons

虽然使用MyClass()创建的对象将是真正的单例对象,但MyClass本身是一个函数,而不是类,因此不能从中调用类方法x=MyClass();y=MyClass();t=类型(n)();

则x==y但x!=t&y!=吨


方法2:基类

class Singleton(object):
    _instance = None
    def __new__(class_, *args, **kwargs):
        if not isinstance(class_._instance, class_):
            class_._instance = object.__new__(class_, *args, **kwargs)
        return class_._instance

class MyClass(Singleton, BaseClass):
    pass

Pros

这是一门真正的课

Cons

多重继承-嗯__new__是否可以在从第二个基类继承期间被覆盖?一个人必须想得比必要的多。


方法3:元类

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

#Python2
class MyClass(BaseClass):
    __metaclass__ = Singleton

#Python3
class MyClass(BaseClass, metaclass=Singleton):
    pass

Pros

这是一门真正的课自动神奇地覆盖继承为正确的目的使用__metaclass__(并让我意识到)

Cons

有吗?


方法4:decorator返回同名类

def singleton(class_):
    class class_w(class_):
        _instance = None
        def __new__(class_, *args, **kwargs):
            if class_w._instance is None:
                class_w._instance = super(class_w,
                                    class_).__new__(class_,
                                                    *args,
                                                    **kwargs)
                class_w._instance._sealed = False
            return class_w._instance
        def __init__(self, *args, **kwargs):
            if self._sealed:
                return
            super(class_w, self).__init__(*args, **kwargs)
            self._sealed = True
    class_w.__name__ = class_.__name__
    return class_w

@singleton
class MyClass(BaseClass):
    pass

Pros

这是一门真正的课自动神奇地覆盖继承

Cons

创建每个新类是否没有开销?在这里,我们为每个希望成为单例的类创建两个类。虽然这对我来说很好,但我担心这可能无法扩展。当然,对于这种模式的规模是否太容易,还有争议。。。_seled属性的意义是什么无法使用super()在基类上调用同名方法,因为它们将递归。这意味着您不能自定义__new__,也不能对需要调用__init__的类进行子类化。


方法5:模块

模块文件singleton.py

Pros

简单胜于复杂

Cons

未延迟实例化


当前回答

一句话(我并不骄傲,但它确实起到了作用):

import sys

class Myclass:
  def __init__(self):
     # do your stuff
      vars(sys.modules[__name__])[type(self).__name__] = lambda: self # singletonify

其他回答

与从元类派生相比,我更喜欢decorator语法。我的两分钱:

from typing import Callable, Dict, Set


def singleton(cls_: Callable) -> type:
    """ Implements a simple singleton decorator
    """
    class Singleton(cls_):  # type: ignore
        __instances: Dict[type, object] = {}
        __initialized: Set[type] = set()

        def __new__(cls, *args, **kwargs):
            if Singleton.__instances.get(cls) is None:
                Singleton.__instances[cls] = super().__new__(cls, *args, **kwargs)
            return Singleton.__instances[cls]

        def __init__(self, *args, **kwargs):
            if self.__class__ not in Singleton.__initialized:
                Singleton.__initialized.add(self.__class__)
                super().__init__(*args, **kwargs)

    return Singleton


@singleton
class MyClass(...):
    ...

这比其他装饰器有一些好处:

isinstance(MyClass(),MyClass)仍然有效(从子句返回函数而不是类将使isinstance失败)属性、classmethod和staticmethod仍将按预期工作__init__()构造函数只执行一次您可以再次使用@singleton从修饰类(无用?)继承

欺骗:

打印(MyClass()__第__类__name__)将返回Singleton而不是MyClass。如果您仍然需要这个,我建议使用上面建议的元类。

如果您需要基于构造函数参数的不同实例,则需要改进此解决方案(由siddhesh suhas sathe提供的解决方案)。

最后,正如其他人所建议的,考虑在python中使用模块。模块是对象。您甚至可以在变量中传递它们,并将它们注入到其他类中。

我会把我的扔到戒指里。这是一个简单的装饰器。

from abc import ABC

def singleton(real_cls):

    class SingletonFactory(ABC):

        instance = None

        def __new__(cls, *args, **kwargs):
            if not cls.instance:
                cls.instance = real_cls(*args, **kwargs)
            return cls.instance

    SingletonFactory.register(real_cls)
    return SingletonFactory

# Usage
@singleton
class YourClass:
    ...  # Your normal implementation, no special requirements.

我认为它比其他一些解决方案有好处:

它清晰简洁(在我看来;D)。它的作用是完全封闭的。你不需要改变YourClass的实现。这包括不需要为类使用元类(请注意,上面的元类在工厂中,而不是“真实”类)。它不依赖猴子修补任何东西。它对呼叫者是透明的:调用者仍然只是简单地导入YourClass,它看起来像一个类(因为它是),并且他们正常使用它。无需使调用者适应工厂函数。YourClass()实例化的仍然是您实现的YourClass的真实实例,而不是任何类型的代理,因此不会产生任何副作用。isinstance(例如,YourClass)和类似的操作仍按预期工作(尽管这个位确实需要abc,因此排除了Python<2.6)。

一个缺点确实出现在我身上:真实类的类方法和静态方法不能通过隐藏它的工厂类透明地调用。我很少使用这种方法,我从来没有遇到过这种需要,但通过在工厂上使用一个自定义元类来实现__getattr__(),将所有的属性访问委托给真实类,这很容易纠正。

我实际上发现了一个更有用的相关模式(我并不是说这类事情经常需要),那就是“唯一”模式,用相同的参数实例化类会得到相同的实例。即“每个参数一个”。上述内容很好地适应了这一点,变得更加简洁:

def unique(real_cls):

    class UniqueFactory(ABC):

        @functools.lru_cache(None)  # Handy for 3.2+, but use any memoization decorator you like
        def __new__(cls, *args, **kwargs):
            return real_cls(*args, **kwargs)

    UniqueFactory.register(real_cls)
    return UniqueFactory

尽管如此,我还是同意一般的建议,如果你认为你需要这些东西中的一件,你真的应该停下来问问自己是否真的需要。

我更喜欢使用静态方法GetInstance()来创建单例对象(也不允许任何其他方法这样做),以强调我使用的是单例设计模式。

import inspect
class SingletonMeta(type):
    __instances = {}
    GET_INSTANCE = 'GetInstance' # class method ussed to create Singleton instance

    def __call__(cls, *args, **kwargs):
        caller_frame = inspect.currentframe().f_back

        caller_class = caller_frame.f_locals.get('cls_ref')
        caller_method_name = caller_frame.f_code.co_name
        if caller_class is cls and \
            caller_method_name == SingletonMeta.GET_INSTANCE:
            obj = super(SingletonMeta, cls).__call__(*args, **kwargs)
        else:
            raise Exception(f"Class '{cls.__name__}' is a singleton! Use '{cls.__name__}.{SingletonMeta.GET_INSTANCE}()' to create its instance.")

        return obj

    def __new__(cls, name, bases, dct):
        def GetInstance(cls_ref):
            if cls_ref not in cls_ref.__instances:
                cls_ref.__instances[cls_ref] = cls_ref()

            return cls_ref.__instances[cls_ref]
       
        return super().__new__(cls, name, bases, {**dct, GetInstance.__name__: classmethod(GetInstance)})
#------------------------
if __name__ == '__main__':
    class SingletonSample1(metaclass=SingletonMeta):
        def __init__(self):
            self.__x = 1

        @property
        def x(self) -> int:
            return self.__x

        @x.setter
        def x(self, value):
            self.__x = value

    s1 = SingletonSample1.GetInstance()
    s1.x = 3

    try:
        s2 = SingletonSample1()
    Exception as error:
        print(repr(error))

我将推荐一个使用元类的优雅解决方案

class Singleton(type): 
    # Inherit from "type" in order to gain access to method __call__
    def __init__(self, *args, **kwargs):
        self.__instance = None # Create a variable to store the object reference
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            # if the object has not already been created
            self.__instance = super().__call__(*args, **kwargs) # Call the __init__ method of the subclass (Spam) and save the reference
            return self.__instance
        else:
            # if object (Spam) reference already exists; return it
            return self.__instance

class Spam(metaclass=Singleton):
    def __init__(self, x):
        print('Creating Spam')
        self.x = x


if __name__ == '__main__':
    spam = Spam(100)
    spam2 = Spam(200)

输出:

Creating Spam

从输出中可以看到,只有一个对象被实例化

经过一段时间的努力,我终于想出了以下方法,这样当从单独的模块调用时,配置对象只能加载一次。元类允许将全局类实例存储在内置dict中,目前看来这是存储适当程序全局的最整洁的方式。

import builtins

# -----------------------------------------------------------------------------
# So..... you would expect that a class would be "global" in scope, however
#   when different modules use this,
#   EACH ONE effectively has its own class namespace.  
#   In order to get around this, we use a metaclass to intercept
#   "new" and provide the "truly global metaclass instance" if it already exists

class MetaConfig(type):
    def __new__(cls, name, bases, dct):
        try:
            class_inst = builtins.CONFIG_singleton

        except AttributeError:
            class_inst = super().__new__(cls, name, bases, dct)
            builtins.CONFIG_singleton = class_inst
            class_inst.do_load()

        return class_inst

# -----------------------------------------------------------------------------

class Config(metaclass=MetaConfig):

    config_attr = None

    @classmethod
    def do_load(cls):
        ...<load-cfg-from-file>...