这个问题不是为了讨论单例设计模式是否可取、是否是一种反模式,或者是否用于任何宗教战争,而是为了讨论如何以最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

未延迟实例化


当前回答

方法:一次性使用后重写__new__

class Singleton():
    def __init__(self):
        Singleton.instance = self
        Singleton.__new__ = lambda _: Singleton.instance

Pros

极其简洁真正的类,不需要模块正确使用lambda和蟒猴补丁

Cons

__new__可能再次被覆盖

其他回答

这个解决方案在模块级别造成了一些命名空间污染(三个定义而不是一个),但我发现很容易理解。

我希望能够编写这样的东西(惰性初始化),但不幸的是,类在它们自己的定义体中不可用。

# wouldn't it be nice if we could do this?
class Foo(object):
    instance = None

    def __new__(cls):
        if cls.instance is None:
            cls.instance = object()
            cls.instance.__class__ = Foo
        return cls.instance

由于这是不可能的,我们可以在

Eagle初始化:

import random


class FooMaker(object):
    def __init__(self, *args):
        self._count = random.random()
        self._args = args


class Foo(object):
    def __new__(self):
        return foo_instance


foo_instance = FooMaker()
foo_instance.__class__ = Foo

延迟初始化:

Eagle初始化:

import random


class FooMaker(object):
    def __init__(self, *args):
        self._count = random.random()
        self._args = args


class Foo(object):
    def __new__(self):
        global foo_instance
        if foo_instance is None:
            foo_instance = FooMaker()
        return foo_instance


foo_instance = None

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

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

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

使用元类

我建议使用方法#2,但最好使用元类而不是基类。下面是一个示例实现:

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]
        
class Logger(object):
    __metaclass__ = Singleton

或在Python3中

class Logger(metaclass=Singleton):
    pass

如果要在每次调用类时运行__init__,请添加

        else:
            cls._instances[cls].__init__(*args, **kwargs)

到Singleton中的if语句__呼叫__。

关于元类的几句话。元类是类的类;也就是说,类是其元类的实例。您可以在Python中找到类型为(obj)的对象元类。正常的新型类是类型类型。上面代码中的Logger类型为“your_module.Singleton”,就像Logger的(唯一)实例类型为“your_module.Logger”一样。当您使用Logger()调用Logger时,Python首先询问Logger的元类Singleton要做什么,允许预先创建实例。这个过程与Python在通过执行myclass.attribute引用类的一个属性时,通过调用__getattr__询问类要做什么相同。

元类本质上决定了类的定义意味着什么以及如何实现该定义。例如,请参见http://code.activestate.com/recipes/498149/,它本质上使用元类在Python中重新创建C样式结构。线程元类的一些(具体)用例是什么?还提供了一些示例,它们通常似乎与声明性编程有关,尤其是在ORM中使用。

在这种情况下,如果您使用方法#2,并且子类定义了__new__方法,那么它将在每次调用SubClassOfSingleton()时执行,因为它负责调用返回存储实例的方法。对于元类,当创建唯一的实例时,它只会被调用一次。您需要自定义调用类的含义,这取决于类的类型。

通常,使用元类来实现单例是有意义的。单例是特殊的,因为它只创建一次,元类是您自定义类创建的方式。在需要以其他方式自定义单例类定义的情况下,使用元类可以提供更多的控制。

您的单例不需要多重继承(因为元类不是基类),但是对于使用多重继承的创建类的子类,您需要确保单例类是第一个/最左边的元类,元类重新定义__call__。这不太可能是问题。实例dict不在实例的命名空间中,因此不会意外覆盖它。

您还会听到单例模式违反了“单一责任原则”——每个类只能做一件事。这样,如果您需要更改另一个代码,您就不必担心会弄乱代码所做的一件事,因为它们是独立的和封装的。元类实现通过了这个测试。元类负责实施模式,创建的类和子类不需要知道它们是单体。方法#1未能通过此测试,正如您在“MyClass本身是一个函数,而不是一个类,因此您不能从中调用类方法”中指出的那样

Python 2和3兼容版本

编写在Python2和Python3中都有效的内容需要使用稍微复杂一些的方案。由于元类通常是类型类型的子类,因此可以使用元类在运行时动态创建一个中间基类,将其作为元类,然后将其用作公共Singleton基类的基类。解释起来比做起来更难,如下图所示:

# works in Python 2 & 3
class _Singleton(type):
    """ A metaclass that creates a Singleton base class when called. """
    _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]

class Singleton(_Singleton('SingletonMeta', (object,), {})): pass

class Logger(Singleton):
    pass

这种方法的一个讽刺之处在于它使用子类来实现元类。一个可能的优点是,与纯元类不同,isinstance(inst,Singleton)将返回True。

更正

在另一个主题上,您可能已经注意到了这一点,但您最初文章中的基类实现是错误的_实例需要在类上引用,您需要使用super()或递归,__new__实际上是一个静态方法,您必须将类传递给它,而不是类方法,因为实际的类在调用时尚未创建。所有这些对于元类实现也是正确的。

class Singleton(object):
  _instances = {}
  def __new__(class_, *args, **kwargs):
    if class_ not in class_._instances:
        class_._instances[class_] = super(Singleton, class_).__new__(class_, *args, **kwargs)
    return class_._instances[class_]

class MyClass(Singleton):
  pass

c = MyClass()

装饰师返回类

我本来是在写一篇评论,但太长了,所以我会在这里加上这个。方法#4比其他装饰器版本更好,但它的代码比单例所需的要多,而且它的作用也不太清楚。

主要问题源于类是其自身的基类。首先,让一个类是一个几乎相同的类的子类,并且只有__class__属性中有相同的名称,这不是很奇怪吗?这也意味着你不能用super()在基类上定义任何调用同名方法的方法,因为它们会递归。这意味着您的类不能自定义__new__,也不能从需要调用__init__的任何类派生。

何时使用单例模式

您的用例是想要使用单例的更好示例之一。你在其中一条评论中说:“对我来说,日志记录似乎一直是辛格尔顿的天然选择。”。

当人们说单身者不好时,最常见的原因是他们是隐性共享状态。虽然全局变量和顶级模块导入是显式共享状态,但传递的其他对象通常是实例化的。这是一个很好的观点,但有两个例外。

第一个,也是在很多地方提到的,是当单重态是恒定的。全局常量的使用,特别是枚举,被广泛接受,并被认为是合理的,因为不管怎样,没有一个用户可以为任何其他用户弄乱它们。这对于一个常量单例也是如此。

第二个例外是相反的,这一点很少提及——当单例只是一个数据接收器,而不是一个数据源(直接或间接)时。这就是为什么伐木工人感觉自己是单身汉的“自然”用途。由于不同的用户并没有以其他用户关心的方式更改日志记录程序,因此没有真正的共享状态。这否定了反对单例模式的主要论点,并使它们成为合理的选择,因为它们易于用于任务。

这里有一段来自http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html:

现在,有一种Singleton是可以的,那就是所有可到达的对象都是不可变的。如果所有对象都是不可变的,那么Singleton就没有全局状态,因为一切都是恒定的。但要将这种单例转换为可变单例是非常容易的,这是非常困难的。因此,我也反对这些单身汉,不是因为他们很坏,而是因为他们很容易变坏。(顺便说一句,Java枚举就是这类单例。只要不在枚举中输入状态,就可以了,所以请不要这样做。)另一种是半可接受的Singleton,它们不影响代码的执行,没有“副作用”。日志记录就是一个很好的例子。它装载着辛格尔顿和全球状态。这是可以接受的(因为它不会伤害您),因为无论是否启用了给定的记录器,您的应用程序的行为都没有任何不同。这里的信息是单向流动的:从应用程序到记录器。即使认为记录器是全局状态,因为没有信息从记录器流到应用程序中,记录器也是可以接受的。如果您希望您的测试断言某些内容正在被记录,您仍然应该注入您的日志,但一般情况下,尽管日志充满了状态,但它不会有害。

我只是偶然做了一个简单的,想分享一下。。。

class MySingleton(object):
    def __init__(self, *, props={}):
        self.__dict__ = props

mything = MySingleton()
mything.test = 1
mything2 = MySingleton()
print(mything2.test)
mything2.test = 5
print(mything.test)

赞成的意见这是一个真正的类Auto神奇地覆盖继承使用元类出于正当目的(并使我意识到)有吗?

这将是串行化的问题。如果您尝试从文件(pickle)反序列化对象,它将不使用__call__,因此将创建新文件,您可以使用__new__的基类继承来防止这种情况。