在Python中创建单例


945

这个问题不是为了讨论是否需要单例设计模式,是否是反模式,还是针对任何宗教战争,而是要讨论如何以最pythonic的方式在Python中最好地实现此模式。在这种情况下,我将“最pythonic”定义为表示它遵循“最少惊讶的原理”

我有多个将成为单例的类(我的用例用于记录器,但这并不重要)。当我可以简单地继承或修饰时,我不希望增加gumph来使几个类杂乱无章。

最佳方法:


方法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

优点

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

缺点

  • 使用MyClass()创建的对象将是真正的单例对象,而MyClass本身是一个函数,而不是类,因此您不能从中调用类方法。也就m = MyClass(); n = MyClass(); o = type(n)();这样m == n && m != o && n != o

方法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

优点

  • 这是一个真正的课堂

缺点

  • 多重继承-好!__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

优点

  • 这是一个真正的课堂
  • 自动神奇地涵盖继承
  • 利用__metaclass__它的正确用途(使我意识到这一点)

缺点

  • 有吗

方法4:装饰器返回具有相同名称的类

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

优点

  • 这是一个真正的课堂
  • 自动神奇地涵盖继承

缺点

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

方法5:一个模块

一个模块文件 singleton.py

优点

  • 简单胜于复杂

缺点


12
另三种技术:代替模块(通常-通常,我认为-这是更适合Python的模式,但是在某种程度上取决于您在使用它做什么);创建一个实例并处理它(foo.x或者,如果您坚持使用Foo.x而不是Foo().x);使用类属性和静态/类方法(Foo.x)。
克里斯·摩根

10
@ChrisMorgan:如果您只打算使用类/静态方法,那么就不用费心制作一个类了。
Cat Plus Plus

2
@Cat:效果是相似的,但是创建全局变量的原因几乎可以是任何事情,包括不知道任何更好的事情。为什么要创建一个单例?如果您不得不问您不应该在这里。这种明确性不仅更具Python特色,而且使维护变得更加简单。是的,单例对于全局变量是语法糖,但是对于一大堆难看的东西,类则是语法糖。我认为没有人会告诉你,没有它们总是更好。
theheadofabroom 2011年

14
@BiggAl:单身是不是 Python的,无论你如何实现它们。它们充其量是一个有缺陷的设计的迹象。
Cat Plus Plus

8
反信号情绪是最糟糕的货运崇拜节目。与听到的人一样(很少有人费心去阅读)“ Goto声明被认为是有害的”,并且认为gotos是不正确代码的标志,而不管上下文如何。
Hejazzman,2015年

Answers:


657

使用元类

我建议使用方法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)

对中的if陈述Singleton.__call__

关于元类的几句话。元类是类的类 ; 也就是说,类是其元类实例。您可以使用来找到Python中对象的元类type(obj)。普通的新式类是类型typeLogger上面代码中的将会是type class 'your_module.Singleton',就像的(唯一的)实例Logger将是type一样class 'your_module.Logger'。当你调用记录仪与Logger(),Python的首先要求的元类LoggerSingleton,做什么,允许实例创建要捷足先登。此过程与Python __getattr__通过执行以下操作引用类的一个属性时调用类来询问类的方法相同:myclass.attribute

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

在这种情况下,如果您使用方法2,并且子类定义了一个__new__方法,则每次调用都会执行SubClassOfSingleton()该方法-因为它负责调用返回存储实例的方法。对于元类,仅在创建唯一实例时才调用一次。您想自定义调用类的含义,该类的类型决定。

通常,使用元类实现单例是有意义的。单例很特别,因为它只能创建一次,而元类是自定义类创建的方式。如果需要以其他方式自定义单例类定义,则使用元类可以提供更多控制权

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

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

Python 2和3兼容版本

编写适用于Python2和3的东西需要使用稍微复杂一些的方案。由于元类通常是type的子类type,因此可以使用一个在运行时动态创建中介基类并将其作为元类,然后将用作公共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

更正

在另一个主题上,您可能已经注意到了这一点,但是原始文章中的基类实现是错误的。_instances需要在类引用,您需要使用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__调用的任何类派生。

何时使用单例模式

您的用例是想要使用单例的更好示例之一。您在其中一项评论中说:“对我而言,伐木一直是Singletons的自然选择。” 你说的

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

第一个,并且在各个地方都被提及的,是单例是恒定的。全局常数(尤其是枚举)的使用已被广泛接受,并被认为是明智的,因为无论如何,任何用户都无法为其他任何用户弄乱它们。对于恒定的单例也同样如此。

第二个例外(相反,它被忽略了)是相反的-当单例仅仅是数据接收器,而不是数据源(直接或间接)时。这就是为什么记录器感觉单例的“自然”使用。由于各种用户没有以其他用户关心的方式更改记录器,因此并没有真正的共享状态。这消除了反对单例模式的主要论点,并使其成为合理的选择,因为它们易于执行任务。

这是来自http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html的报价:

现在,有一种Singleton可以。那是所有可达对象都是不可变的单例。如果所有对象都是不可变的,则Singleton没有全局状态,因为一切都是恒定的。但是将这种单身人士变成易变的人是如此容易,这是很滑的坡度。因此,我也反对这些Singleton,不是因为它们不好,而是因为它们很容易变坏。(作为一个附带说明,Java枚举就是这些单例。只要您不将状态放入枚举中就可以,所以请不要这样做。)

另一种半可接受的单例是那些不影响代码执行的单例,它们没有“副作用”。日志记录就是一个很好的例子。它加载了Singletons和全局状态。这是可以接受的(因为它不会伤害您),因为无论是否启用给定的记录器,您的应用程序的行为都没有任何不同。此处的信息以一种方式流动:从您的应用程序进入记录器。甚至认为记录器是全局状态,因为没有信息从记录器流入您的应用程序,所以记录器是可以接受的。如果您想让测试断言某些东西正在被记录,那么您仍然应该注入记录器,但是一般来说,记录器即使处于满状态也不会有害。


5
不,单身人士永远都不好。日志记录可能是成为全局记录的好方法(尽管如此),但肯定不是单例。
Cat Plus Plus

11
看看googletesting.blogspot.com/2008/08/…。它通常是反单顿的(有充分的理由),但是它很好地解释了为什么不变的单身人士和没有副作用的单身人士不会遇到相同的问题(如果您要小心的话)。我将在帖子末尾引用它。
2011年

4
我的单例问题是“仅一个实例”的愚蠢前提。这和大量的线程安全问题。和依赖隐藏。全局变量不好,单例只是具有更多问题的全局变量。
Cat Plus Plus

4
@猫单身人士有很好的用途。硬件模块的延迟实例化(尤其是在单线程应用程序中)是其中之一(但也存在线程安全的单例)。
保罗·曼塔

3
__new__元类中的@Alcott 是是新的-定义时,而不是实例是新时。调用类(MyClass())是您要覆盖的操作,而不是类的定义。如果您真的想了解Python的工作原理,那么除了继续使用它以外,最好的方法是阅读docs.python.org/reference/datamodel.html。关于元类的一个很好的参考是eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example。关于单例的一篇不错的文章是我在此答案中链接的Google博客的系列文章。
2011年

91
class Foo(object):
     pass

some_global_variable = Foo()

模块仅导入一次,其他一切都考虑不周。不要使用单例并且不要使用全局。


14
你为什么说“不使用单身人士”?任何原因?
奥尔科特

3
如果必须腌制单例,这将不起作用。使用您提供的示例:s = some_global_variable; str = pickle.dumps(s); s1 = pickle.loads(str); print s is s1; # False
bybyzero 2012年

4
@dividebyzero:is操作员测试指针是否相等。如果pickle.loads返回对先前存在的对象的引用,而不是对新创建的对象的引用,我会感到很惊讶-甚至将其称为错误- 。因此,测试是否s is s1不会告诉您使用模块作为单例的适用性。
乔纳斯·科尔克(JonasKölker)2014年

1
@JonasKölker pickle.loads()已经做到了,例如对于bool和的实例NoneTypepickle.loads(pickle.dumps(False)) is False收益率True
Dan Passaro 2014年

2
@ leo-the-manic:公平点;然而,这就是实习的对象的Python只是一个副作用TrueFalse并且None和无关的代码后面pickle.loads。另外,只对只读对象也很安全。如果pickle.loads要返回对已经存在的可修改对象(例如模块)的引用,那将是一个错误。(因此,我的意思是dividbyzero的代码示例没有任何内容。)
JonasKölker2014年

69

使用模块。它仅导入一次。在其中定义一些全局变量-它们将是单例的“属性”。添加一些功能-单例的“方法”。


11
因此,您最终得到的是...不是一堂课。您不能将其用作一个类,不能在其上建立其他类,使用导入语法,突然之间您失去了OOP的所有优势...
theheadofabroom 2011年

16
如果可以在其上建立其他类,则可能不是单例。您可以创建派生类之一,也可以创建基类之一,但是派生类也是基类的成员,并且您有两个基类,您应该使用哪个?
SingleNegationElimination

这不适用于所有模块。在“主”模块中,我设置了一个值。然后,我在另一个模块中引用它,并且它为null。叹。
Paul Kenjora's

1
@PaulKenjora您的代码中必须有一个错误。如果在模块中定义了全局变量,则当您从另一个模块访问它时,该变量应具有值。
warvariuc

它具有值,但是当我更改它时,它不会保留。我最终在有效的模块中使用了具有属性的类。像全局变量这样的简单类型对我不起作用(范围更改后,它们便失去了值)。
Paul Kenjora '17

29

您可能永远不需要Python中的单例。只需在一个模块中定义所有数据和功能,便拥有事实上的单例。

如果您真的绝对必须要有一个单例课程,那么我可以考虑:

class My_Singleton(object):
    def foo(self):
        pass

my_singleton = My_Singleton()

使用方法:

from mysingleton import my_singleton
my_singleton.foo()

其中mysingleton.py是定义My_Singleton的文件名。之所以起作用,是因为第一次导入文件后,Python不会重新执行代码。


4
通常是正确的,但有时还不够。例如,我有一个项目,需要在DEBUG级别记录许多类的实例化。我需要在启动时解析命令行选项,以便实例化这些类之前设置用户指定的日志记录级别。模块级别的实例化使该问题变得棘手。我可能会仔细构造应用程序,以便在完成CLI处理之前不会导入所有这些类,但是我的应用程序的自然结构比信条地遵循“单句不好”更为重要,因为它们可以做得很干净。
CryingCyclops

如果要在修补my_singleton的同时测试代码,那有可能吗?因为可以在其他模块中实例化此my_singleton。
Naveen

@Naveen-my_singleton是单个对象。如果您“修补”了该更改,则将影响所有以后的引用,即使在其他模块中也是如此。
艾伦·戴克

16

这是您的一线客:

singleton = lambda c: c()

使用方法如下:

@singleton
class wat(object):
    def __init__(self): self.x = 1
    def get_x(self): return self.x

assert wat.get_x() == 1

您的对象会被实例化。这可能是您想要的,也可能不是。


5
-1。这是非常糟糕和愚蠢的。您没有定义用作对象的类。您不能再像type(wat)或一样丑陋地访问课程wat.__class__。如果您确实想实现此目的,则可以更好地定义该类并立即实例化它,而无需搞砸装饰器。
0xc0de 2014年

2
为什么您需要知道使用单例的类?只需使用单例对象
即可。.– Tolli

1
这不是单例模式,因此IMO函数的名称应不同。
2014年

8
维基百科:“单例模式是一种将类的实例化限制为一个对象的设计模式”。我会说我的解决方案就是这样做的。好的,我想一个人可以做wat2 = type(wat)(),但这是python,我们都同意大人以及所有这些。您不能保证只有一个实例,但是可以保证,如果人们再提出一个实例,它将看起来很丑陋,并且-如果他们是体面的,正直的人-就像对他们的警告标志。我想念什么?
JonasKölker'15


3

这是我自己的单例实现。您所要做的就是装饰教室。要获得单例,则必须使用该Instance方法。这是一个例子:

   @Singleton
   class Foo:
       def __init__(self):
           print 'Foo created'

   f = Foo() # Error, this isn't how you get the instance of a singleton

   f = Foo.Instance() # Good. Being explicit is in line with the Python Zen
   g = Foo.Instance() # Returns already created instance

   print f is g # True

这是代码:

class Singleton:
    """
    A non-thread-safe helper class to ease implementing singletons.
    This should be used as a decorator -- not a metaclass -- to the
    class that should be a singleton.

    The decorated class can define one `__init__` function that
    takes only the `self` argument. Other than that, there are
    no restrictions that apply to the decorated class.

    To get the singleton instance, use the `Instance` method. Trying
    to use `__call__` will result in a `TypeError` being raised.

    Limitations: The decorated class cannot be inherited from.

    """

    def __init__(self, decorated):
        self._decorated = decorated

    def Instance(self):
        """
        Returns the singleton instance. Upon its first call, it creates a
        new instance of the decorated class and calls its `__init__` method.
        On all subsequent calls, the already created instance is returned.

        """
        try:
            return self._instance
        except AttributeError:
            self._instance = self._decorated()
            return self._instance

    def __call__(self):
        raise TypeError('Singletons must be accessed through `Instance()`.')

    def __instancecheck__(self, inst):
        return isinstance(inst, self._decorated)

2
这不是真正的单身人士。SingletonList = Singleton(list).Instance(); print(SingletonList is type(SingletonList)())应该True以真实单例打印;带有您的代码打印件False
GingerPlusPlus 2014年

@GingerPlusPlus我知道一些限制,但没有意识到您指出的限制。感谢您提及。不幸的是,我现在没有时间解决这个问题。
保罗·曼塔

2

方法3看起来很整洁,但是如果您希望程序同时在Python 2Python 3中运行,那么它将无法正常工作。即使使用Python版本的测试来保护单独的变体也失败了,因为Python 3版本在Python 2中给出了语法错误。

感谢Mike Watkins:http : //mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/。如果要使程序在Python 2和Python 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]

MC = Singleton('MC', (object), {})

class MyClass(MC):
    pass    # Code for the class implementation

我认为作业中的“对象”需要替换为“ BaseClass”,但是我还没有尝试过(我尝试了如图所示的代码)。


当然,这不是元类-在python3中使用元类构造MyClass会做class MyClass(metaclass=Singleton)
theheadofabroom 2013年

mikewatkins.ca链接(有效)打破。
Peter Mortensen

1

好吧,除了同意关于模块级全局的Pythonic通用建议外,如何做到这一点:

def singleton(class_):
    class class_w(class_):
        _instance = None
        def __new__(class2, *args, **kwargs):
            if class_w._instance is None:
                class_w._instance = super(class_w, class2).__new__(class2, *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(object):
    def __init__(self, text):
        print text
    @classmethod
    def name(class_):
        print class_.__name__

x = MyClass(111)
x.name()
y = MyClass(222)
print id(x) == id(y)

输出为:

111     # the __init__ is called only on the 1st time
MyClass # the __name__ is preserved
True    # this is actually the same instance

_sealed属性的意义是什么?据我所知,这没有任何作用?某事让我that不安,这表明它的性能不佳...本周晚些时候,我将进行一些比较测试。
theheadofabroom

_sealed确保您的init只运行一次;我看不出为什么它的性能要比常规的函数式装饰器还要差-该函数每个类仅执行一次,并返回一个新的继承类
Guard

顺便说一句,您的编辑包含打破缩进的选项卡您还说“我们正在创建2个类”-您是说我们正在创建“另外1个类”吗?
守卫

是的,我的意思是多一堂课。我打算在__init__每次初始化时都包含要调用的东西。只是一个简单的“在class.method中初始化”。至于缩进-您使用了制表符和空格-我已修复了大多数问题,但如果您想获取它,就好像错过了一个(只是查看编辑日志)
theheadofabroom 2011年

重新初始化:当然是你的,我只是试图模仿其他语言的单身的行为,在构造函数代码(这是不完全初始化,但非常接近它的意义上)只调用一次,如果你想在初始化是每次调用时,只需杀死对_sealed re空格/制表符的所有引用-嗯,那么我的emacs需要修复。无论如何,以上是更正后的版本
Guard

1

这个怎么样:

def singleton(cls):
    instance=cls()
    cls.__new__ = cls.__call__= lambda cls: instance
    cls.__init__ = lambda self: None
    return instance

将其用作应该为单例的类上的装饰器。像这样:

@singleton
class MySingleton:
    #....

这类似于singleton = lambda c: c()另一个答案中的装饰器。与其他解决方案一样,唯一的实例具有类(MySingleton)的名称。但是,使用此解决方案,您仍然可以通过执行从类“创建”实例(实际上是唯一的实例)MySingleton()。它还会阻止您这样做来创建其他实例type(MySingleton)()(这也会返回相同的实例)。


您没有定义将其用作对象的类。
0xc0de 2014年

1
每次调用type(MySingleton)()MySingleton.__init__()都会被调用,并且该对象将多次初始化。你可以cls.__init__ = lambda self: pass用你的文字修复它singleton。而且,覆盖cls.__call__似乎毫无意义,甚至__call__在调用时MySingleton(any, list, of, arguments)(而不是在调用时)在此上下文中使用了有害定义type(MySingleton)(any, list, of, arguments)
GingerPlusPlus 2014年

@GingerPlusPlus,感谢您指出__init__()这样做时再次被调用type(MySingleton)()。您建议的解决方案(添加cls.__init__ = lambda self: pass)给出了语法错误,因为lambda表达式的最后一部分需要是表达式,而不是语句。但是,添加了cls.__init__ = lambda self: None作品,因此我将其添加到了答案中。
Tolli 2015年

1
@GingerPlusPlus,关于的使用__call__。我的意图是同时做出type(MySingleton)()MySingleton()返回实例。所以它正在做我想要的。您可以将MySingleton视为单例的类型或单例的实例(或两者)。
Tolli 2015年

1

我将我扔进戒指。这是一个简单的装饰器。

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。这包括不需要为您的类使用元类(请注意,上面的元类在工厂中,而不是“真实”类)。
  • 它不依赖于猴子修补任何东西。
  • 对呼叫者透明:
    • 调用者仍然简单地import YourClass,它看起来像一个类(因为是),并且可以正常使用它。无需使调用者适应工厂功能。
    • 什么YourClass()实例仍是的一个真正的实例YourClass您实现的,没有任何形式的代理,所以没有副作用的几率从产生。
    • isinstance(instance, 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

综上所述,我确实同意以下一般性建议:如果您认为自己需要这些东西之一,则可能应该停一会儿,问问自己是否确实需要。YAGNI 99%的时间。


0

基于Tolli的答案的代码。

#decorator, modyfies new_cls
def _singleton(new_cls):
    instance = new_cls()                                              #2
    def new(cls):
        if isinstance(instance, cls):                                 #4
            return instance
        else:
            raise TypeError("I can only return instance of {}, caller wanted {}".format(new_cls, cls))
    new_cls.__new__  = new                                            #3
    new_cls.__init__ = lambda self: None                              #5
    return new_cls


#decorator, creates new class
def singleton(cls):
    new_cls = type('singleton({})'.format(cls.__name__), (cls,), {} ) #1
    return _singleton(new_cls)


#metaclass
def meta_singleton(name, bases, attrs):
    new_cls = type(name, bases, attrs)                                #1
    return _singleton(new_cls)

说明:

  1. 创建新类,继承自给定的类cls
    cls例如,在有人想要的情况下,它不会进行修改singleton(list)

  2. 创建实例。覆盖之前__new__是如此简单。

  3. 现在,当我们轻松创建实例后,请__new__使用之前定义的方法进行覆盖。
  4. 该函数instance仅在调用者期望的时候返回,否则抛出TypeError
    当有人尝试从装饰类继承时,不满足该条件。

  5. 如果__new__()返回的实例cls,那么新实例的__init__()方法将被调用一样__init__(self[, ...]),这里的自我是新实例,其余参数都一样传递给__new__()

    instance已经被初始化,所以__init__功能被什么都不做的功能所取代。

看到它在线上工作


0

它与晶圆厂的答案有些相似,但并不完全相同。

单合同并不要求我们能够多次调用构造函数。作为一个单例应该仅创建一次,是否不应该将其视为仅创建一次?“欺骗”构造函数无疑会损害可读性。

所以我的建议是这样的:

class Elvis():
    def __init__(self):
        if hasattr(self.__class__, 'instance'):
            raise Exception()
        self.__class__.instance = self
        # initialisation code...

    @staticmethod
    def the():
        if hasattr(Elvis, 'instance'):
            return Elvis.instance
        return Elvis()

这不排除instance用户代码对构造函数或字段的使用:

if Elvis() is King.instance:

...如果您确定Elvis还没有创建,那就已经创建了King

但它鼓励用户the普遍使用该方法:

Elvis.the().leave(Building.the())

为了完成此操作,__delattr__()如果尝试删除instance,也可以重写以引发Exception ,并重写__del__()以引发Exception(除非我们知道程序正在结束...)

进一步的改进


感谢那些为评论和编辑提供帮助的人,我们欢迎其中的更多内容。当我使用Jython时,这应该更通用,并且是线程安全的。

try:
    # This is jython-specific
    from synchronize import make_synchronized
except ImportError:
    # This should work across different python implementations
    def make_synchronized(func):
        import threading
        func.__lock__ = threading.Lock()

        def synced_func(*args, **kws):
            with func.__lock__:
                return func(*args, **kws)

        return synced_func

class Elvis(object): # NB must be subclass of object to use __new__
    instance = None

    @classmethod
    @make_synchronized
    def __new__(cls, *args, **kwargs):
        if cls.instance is not None:
            raise Exception()
        cls.instance = object.__new__(cls, *args, **kwargs)
        return cls.instance

    def __init__(self):
        pass
        # initialisation code...

    @classmethod
    @make_synchronized
    def the(cls):
        if cls.instance is not None:
            return cls.instance
        return cls()

注意事项:

  1. 如果您不从python2.x中的对象继承子类,则将获得一个老式的类,该类不使用 __new__
  2. 装饰时,__new__您必须使用@classmethod装饰,否则__new__它将是未绑定的实例方法
  3. 可以通过使用元类来改善这一点,因为这将使您能够创建the类级别的属性,并将其重命名为instance

尽管这与单例模式的解释稍有不同,但我可以肯定它仍然有效,尽管我可能会尝试使用__new__而不是__init__,因为它纯粹作用于类属性,这可以防止在那里短暂地成为第二个实例。此方法与方法2之间的区别在于,尝试初始化一次以上是否返回单个实例还是引发异常。我想我很高兴能满足单例模式,一个更易于使用,而另一个更明确地表明它是单例。
theheadofabroom

显然,在中使用类名称__init__可以防止子类化,但这虽然使事情变得容易,但这不是必需的
theheadofabroom

谢谢...啊,是的,在抛出异常之前的第二个瞬间。我已对其进行了修改,__init__以期希望它应该是可细分的……
mike rodent

the

你是对的。然后,您可以拥有SuperElvis子类单例和(例如)ImaginaryElvis子类单例……它们可以共存。查看其他想法。请随时改进我的代码。
迈克·罗丹,

0

一名班轮(我不为此感到自豪,但确实能胜任):

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

只要您的课程尚未导入任何其他模块,这就可以完成工作……
Aran-Fey

真正。如果您可以支付初始化类的费用,则可以在类定义之后立即运行Myclass()来避免这种情况。
polvoazul

0

如果您不需要懒惰地初始化Singleton实例,则以下操作应该很容易且线程安全:

class A:
    instance = None
    # Methods and variables of the class/object A follow
A.instance = A()

这种方式A是在模块导入时初始化的单例。


0
  • 如果一个人想拥有多个相同类的实例,但是只有当args或kwargs不同时,才可以使用
  • 例如
    1. 如果您有课程处理 serial通信,并且要创建一个实例,并且希望将串行端口作为参数发送,那么使用传统方法将无法正常工作
    2. 使用上述装饰器,如果args不同,则可以创建该类的多个实例。
    3. 对于相同的参数,装饰器将返回已经创建的相同实例。
>>> from decorators import singleton
>>>
>>> @singleton
... class A:
...     def __init__(self, *args, **kwargs):
...         pass
...
>>>
>>> a = A(name='Siddhesh')
>>> b = A(name='Siddhesh', lname='Sathe')
>>> c = A(name='Siddhesh', lname='Sathe')
>>> a is b  # has to be different
False
>>> b is c  # has to be same
True
>>>

0

也许我误解了单例模式,但是我的解决方案是这个简单而实用的(pythonic?)。该代码实现了两个目标

  1. 使实例可在Foo任何地方(全局)访问。
  2. 只能Foo存在一个实例。

这是代码。

#!/usr/bin/env python3

class Foo:
    me = None

    def __init__(self):
        if Foo.me != None:
            raise Exception('Instance of Foo still exists!')

        Foo.me = self


if __name__ == '__main__':
    Foo()
    Foo()

输出量

Traceback (most recent call last):
  File "./x.py", line 15, in <module>
    Foo()
  File "./x.py", line 8, in __init__
    raise Exception('Instance of Foo still exists!')
Exception: Instance of Foo still exists!

0

经过一段时间的努力,我最终想到了以下内容,以便从单独的模块中调用配置对象时,它只会被加载一次。元类允许将全局类实例存储在内置指令中,这在当前看来是存储适当程序全局的最简洁的方法。

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>...

-1

我不记得在哪里找到该解决方案,但是从我的非Python专家的角度来看,它是最“优雅”的:

class SomeSingleton(dict):
    __instance__ = None
    def __new__(cls, *args,**kwargs):
        if SomeSingleton.__instance__ is None:
            SomeSingleton.__instance__ = dict.__new__(cls)
        return SomeSingleton.__instance__

    def __init__(self):
        pass

    def some_func(self,arg):
        pass

我为什么喜欢这个?没有装饰器,没有元类,没有多重继承...,如果您决定不再希望它成为Singleton,只需删除该__new__方法。由于我是Python(和OOP的新手)的新手,所以我希望有人能使我明白为什么这是一种糟糕的方法?


2
为什么这是一个可怕的方法?当您要创建另一个单例类时,必须复制并粘贴__new__不要重复自己
2014年

另外,为什么您的新手*args**kwargs和对它们什么都不用?将它们传递给dict.__new__这种方式:dict.__new__(cls, *args, **kwargs)
2014年

__init__每次调用该类时,都会调用该方法。如果您的__init__方法确实做了一些事情,您会注意到问题所在。每当您执行时SomeSingleton(),该__init__方法都会重置您的单例状态。
阿兰·菲

-2

这是我实现单例的首选方式:

class Test(object):
    obj = None

    def __init__(self):
        if Test.obj is not None:
            raise Exception('A Test Singleton instance already exists')
        # Initialization code here

    @classmethod
    def get_instance(cls):
        if cls.obj is None:
            cls.obj = Test()
        return cls.obj

    @classmethod
    def custom_method(cls):
        obj = cls.get_instance()
        # Custom Code here

1
严格来说,这不是单例,因为它允许存在多个类的实例。一种改进是使该类无法初始化,并使所有类方法都对类属性起作用
theheadofabroom 2014年

-2

这个答案可能不是您想要的。我想要一个单例,因为只有那个对象才具有其身份,以便进行比较。就我而言,它被用作前哨值。答案很简单,mything = object()根据python的性质制作任何对象,只有该对象才具有其标识。

#!python
MyNone = object()  # The singleton

for item in my_list:
    if item is MyNone:  # An Example identity comparison
        raise StopIteration

我了解到,模块实际上可以多次导入,在这种情况下,这仅仅是一个本地单例,实际上并不是任何容量的单例。
ThorSummoner

您能否详细说明如何多次导入模块?我唯一看到的是在加载模块时发生异常,用户仍然可以稍后加载模块,但副作用已经发生,因此某些操作可能会第二次执行。
sleblanc

模块完全加载后,除了通过使用eval或强制解释器执行该模块外,我没有其他方法可以再次运行该模块importlib.reload
sleblanc

-3

该解决方案在模块级别上导致了一些名称空间污染(三个定义,而不仅仅是一个定义),但是我发现很容易遵循。

我希望能够编写这样的内容(延迟初始化),但不幸的是,类在其自身的定义中不可用。

# 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

由于这是不可能的,因此我们可以在其中分解初始化和静态实例

急切的初始化:

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

延迟初始化:

急切的初始化:

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.