ChatGPT解决这个技术问题 Extra ChatGPT

在 Python 中创建单例

这个问题不是为了讨论 singleton design pattern 是否可取、是否是反模式或任何宗教战争,而是讨论如何在 Python 中以如下方式最好地实现该模式:最蟒蛇。在这种情况下,我将“最 Pythonic”定义为它遵循“最小惊讶原则”

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

最佳方法:

方法一:装饰器

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 本身是一个函数,而不是类,因此您不能从中调用类方法。也适用于 x = MyClass(); y = MyClass(); t = 类型(n)();

然后 x == yx != t && y != t

方法二:基类

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__ 用于正确目的(并让我意识到)

缺点

有吗?

方法四:装饰器返回同名类

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

优点

简单胜于复杂

缺点

没有延迟实例化

另外三种技术:使用模块代替(通常——我认为,通常——这对 Python 来说是更合适的模式,但它有点取决于你用它做什么);创建一个实例并改为处理它(foo.x 或者如果您坚持使用 Foo.x 而不是 Foo().x);使用类属性和静态/类方法 (Foo.x)。
@ChrisMorgan:如果您只想使用类/静态方法,那么真的不用费心制作一个类。
@Cat:效果相似,但是创建全局变量背后的原因几乎可以是任何事情,包括不知道更好。为什么要创建一个单例?如果你不得不问你不应该在这里。这种明确性不仅更加 Pythonic,而且使维护更加简单。是的,单例是全局变量的语法糖,但是类是一大堆难看的东西的语法糖,我认为没有人会告诉你,没有它们你总是会更好。
反信号灯的情绪是最糟糕的货运狂热节目。人们听到(很少有人真正阅读)“Goto 语句被认为有害”并认为 goto 是错误代码的标志,无论上下文如何。
您好,感谢您精心制作的帖子。实际上,我对模式编程和 python 还很陌生,令我惊讶的是,尽管方法 2 似乎是最知名的一种(它无处不在),但几乎没有人提到尽管只创建了一个对象,但 init__() 被称为每个时间 Singleton() 或 MyClass() 可以在任何地方使用。我没有尝试,但是AFAIK对于所有其他方法也是如此。在实现单例时这似乎不太理想,或者我错过了什么?当然,解决方案包括设置一个属性以避免执行 __init 两次。只是好奇

C
Community

使用元类

我会推荐方法#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.__call__ 中的 if 语句。

关于元类的几句话。元类是类的类;也就是说,类是其元类的实例。您可以使用 type(obj) 在 Python 中找到对象的元类。普通的新式类是 type 类型。上面代码中的 Logger 将是 class 'your_module.Singleton' 类型,就像 Logger 的(唯一)实例将是 class 'your_module.Logger' 类型一样。当你用 Logger() 调用 logger 时,Python 首先询问 LoggerSingleton 的元类,要做什么,允许实例创建被抢占。此过程与当您通过执行 myclass.attribute 引用某个类的属性时,Python 通过调用 __getattr__ 询问该类做什么是相同的。

元类本质上决定了类的定义意味着什么以及如何实现该定义。参见示例 http://code.activestate.com/recipes/498149/,它实质上是使用元类在 Python 中重新创建 C 风格的 struct。线程 What are some (concrete) use-cases for metaclasses? 还提供了一些示例,它们通常似乎与声明式编程有关,尤其是在 ORM 中使用时。

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

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

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

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

Python 2 和 3 兼容版本

编写适用于 Python2 和 3 的东西需要使用稍微复杂的方案。由于元类通常是 type 类型的子类,因此可以使用一个在运行时动态创建一个中间基类,并将其作为元类,然后使用 that 作为公共 {2 } 基类。解释起来比做起来难,如下图所示:

# 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__ 的类。

何时使用单例模式

您的用例是想要使用单例的更好示例之一。您在其中一条评论中说“对我来说,日志记录似乎一直是单身人士的自然候选人。”你是绝对正确的。

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

第一个,也是在不同地方提到的一个,是当单例是常量时。全局常量的使用,尤其是枚举,被广泛接受,并且被认为是理智的,因为无论如何,没有一个用户可以为任何其他用户搞砸它们。这同样适用于常量单例。

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

以下是来自 http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html 的引述:

现在,有一种单例是可以的。这是一个单例,其中所有可访问的对象都是不可变的。如果所有对象都是不可变的,那么 Singleton 就没有全局状态,因为一切都是不变的。但是很容易把这种单例变成可变单例,很滑坡。因此,我也反对这些 Singletons,不是因为它们不好,而是因为它们很容易变坏。 (作为旁注,Java 枚举就是这类单例。只要您不将状态放入枚举中就可以了,所以请不要。)另一种半可接受的单例是那些这不会影响您的代码的执行,它们没有“副作用”。日志记录就是一个很好的例子。它加载了单例和全局状态。这是可以接受的(因为它不会伤害您),因为无论是否启用给定的记录器,您的应用程序的行为都没有任何不同。这里的信息流向一种方式:从您的应用程序到记录器。即使认为记录器是全局状态,因为没有信息从记录器流入您的应用程序,记录器也是可以接受的。如果您希望您的测试断言正在记录某些内容,您仍然应该注入您的记录器,但一般来说,尽管记录器充满了状态,但它并没有害处。


不,单身人士永远都不好。日志记录可能是成为全球性的一个很好的候选者(尽管它们很糟糕),但肯定不是单例的。
查看googletesting.blogspot.com/2008/08/…。它通常是反单例的(有充分的理由),但它很好地解释了为什么不可变单例和没有副作用的单例没有相同的问题,如果你小心的话。我将在我的帖子末尾引用它。
我对单例的问题是“只有一个实例”的愚蠢前提。那和一吨的线程安全问题。和依赖隐藏。全局变量很糟糕,而单例只是具有更多问题的全局变量。
@Cat 单例有很好的用途。硬件模块的延迟实例化(尤其是在单线程应用程序中)就是其中之一(但也存在线程安全的单例)。
元类中的@Alcott __new__ 是当 class 是新的 - 当它被定义时,而不是当 instance 是新的时。调用类 (MyClass()) 是您要覆盖的操作,而不是类的定义。如果您真的想了解 Python 的工作原理,最好的办法(除了继续使用它)就是阅读 docs.python.org/reference/datamodel.html。关于元类的一个很好的参考是 eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example。一篇关于单例的好文章是我在这个答案中链接的谷歌博客的系列。
C
Cat Plus Plus
class Foo(object):
     pass

some_global_variable = Foo()

模块只导入一次,其他一切都想多了。不要使用单例并且尽量不要使用全局变量。


你为什么说“不要使用单例”?任何原因?
如果必须腌制单例,这将不起作用。使用您给出的示例:s = some_global_variable; str = pickle.dumps(s); s1 = pickle.loads(str); print s is s1; # False
@dividebyzero:is 运算符测试指针是否相等。如果 pickle.loads 返回对预先存在的对象的引用而不是对新创建的对象的引用,我会感到相当惊讶——甚至称其为错误。因此,测试 s is s1 是否不能告诉您任何关于将模块用作单例的适用性。
@leo-the-manic:公平点;但是,这只是 Python 对对象 TrueFalseNone 进行实习的副作用,与 pickle.loads 后面的代码无关。此外,只对只读对象进行操作是安全的。如果 pickle.loads 返回对已经存在的 modifiable 对象(例如模块)的引用,那将是一个错误。 (所以我坚持我的暗示,即 dividebyzero 的代码示例并不能证明任何事情。)
我使用 import ABC as X 将模块导入了几个不同的脚本文件(所有这些文件都将使用“singleton”),并且实际上对于每个具有“singleton”模块的 import 语句的脚本都导入了一次。因此,每次导入模块时,我定义的变量都会重新分配其值。我还尝试删除别名,甚至将其导入项目的每个文件中,结果相同。我在 Python 3.9.6 上。带有元类的单例(来自 agf 的回答)对于我的用例(日志记录,必须在编译时初始化一次的变量)完成了绝妙的技巧
w
warvariuc

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


所以你最终得到的是......不是一个班级。你不能把它当作一个类来使用,你不能把它作为其他类的基础,你使用 import 语法,突然之间你就失去了 OOP 的所有好处......
如果您可以在其上建立其他类,那么它可能不是单例。您可以创建一个派生类,也可以创建一个基类,但派生类也是基类的成员,并且您有两个基类,您应该使用哪一个?
@PaulKenjora 您的代码中一定有错误。如果您在一个模块中定义一个全局变量,当您从另一个模块访问它时,它应该具有该值。
@theheadofabroom 你可以import * from base_module...重新考虑 OOP 我的朋友!哈哈哈
你怎么能用模块中的参数来初始化一个单例对象呢?
N
Neuron

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

import datetime
file_name=None

def set_file_name(new_file_name: str):
    global file_name
    file_name=new_file_name

def write(message: str):
    global file_name
    if file_name:
        with open(file_name, 'a+') as f:
            f.write("{} {}\n".format(datetime.datetime.now(), message))
    else:
        print("LOG: {}", message)

要使用:

import log
log.set_file_name("debug.log")
log.write("System starting")
...

如果你真的必须有一个单例课程,那么我会选择:

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

my_singleton = MySingleton()

要使用:

from mysingleton import my_singleton
my_singleton.foo()

其中 mysingleton.py 是定义 MySingleton 的文件名。这是因为第一次导入文件后,Python 不会重新执行代码。


大部分是真的,但有时这还不够。例如,我有一个项目需要在 DEBUG 级别记录许多类的实例化。我需要在启动时解析命令行选项,以便在实例化这些类之前设置用户指定的日志记录级别。模块级实例化使这成为问题。有可能我可以仔细构建应用程序,以便在 CLI 处理完成之前不会导入所有这些类,但我的应用程序的自然结构比教条地坚持“单例不好”更重要,因为它们可以做得很干净。
如果您要在修补 my_singleton 时测试您的代码,这可能吗?因为这个 my_singleton 可以在其他模块中实例化。
@Naveen - my_singleton 是一个对象。如果您“修补”它,该更改将影响所有未来的引用,即使在其他模块中也是如此。
这在某些情况下可能有效,但有时延迟初始化很重要。在我的用例中,初始化需要 400 毫秒,所以我不想仅仅因为我导入了模块而发生这种情况。只有在真正需要单例时才需要这样做。
@乔尼斯。同意对于每个可能的用例都没有完美的解决方案。也许,您仍然可以对代码中耗时的部分使用延迟初始化,而不是将其放入构造函数中。或者,也许您需要此页面上的其他更复杂的建议之一。
J
Jonas Kölker

这是给你的单线:

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

您的对象被急切地实例化。这可能是也可能不是您想要的。


为什么你需要知道使用单例的类?只需使用单例对象..
它不是 singleton pattern,因此 IMO 函数应该以不同的方式命名。
维基百科:“单例模式是一种将类的实例化限制为一个对象的设计模式”。我会说我的解决方案就是这样做的。好的,我想可以做 wat2 = type(wat)(),但这是 python,我们都是同意的成年人等等。你不能保证只有一个实例,但你可以保证如果人们再做第二个,它看起来会很丑,而且——如果他们是正派、正直的人——就像一个警告给他们签名。我错过了什么?
如果您真的在寻找单线解决方案,请尝试将 python 模块作为单例,这实际上是一种零线解决方案。
C
Community

查看 Stack Overflow 问题 Is there a simple, elegant way to define singletons in Python? 以及几个解决方案。

我强烈建议观看 Alex Martelli 关于 Python 设计模式的演讲:part 1part 2。特别是,在第 1 部分中,他谈到了单例/共享状态对象。


虽然这并不是我问题的真正答案,但您指出的资源非常有用。我不情愿地给你+1
b
buhtz

如果想拥有多个同一个类的实例,但前提是args或kwargs不同,可以使用第三方python包Handy Decorators(包装饰器)。

前任。如果您有一个处理串行通信的类,并且要创建一个您想将串行端口作为参数发送的实例,那么使用传统方法将不起作用使用上述装饰器,如果参数是不同的。对于相同的参数,装饰器将返回已创建的相同实例。

如果您有一个处理串行通信的类,并且要创建一个您想将串行端口作为参数发送的实例,那么使用传统方法将不起作用

使用上面提到的装饰器,如果参数不同,可以创建类的多个实例。

对于相同的参数,装饰器将返回已创建的相同实例。

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

需要制作 this 类型的单例是导致我提出这个问题的原因。非常感激!我尝试了 pip install handy-decorators,得到了 ERROR: Could not find a version that satisfies the requirement handy-decorators。有什么建议吗?
我继续从 here 复制源代码并装饰了一个数据类。它第一次起作用。令人高兴的是,它不依赖于任何其他代码!该模块中的所有内容都非常简单明了,真正的 Pythonic。如果你不教 Python,你应该教。
警告:@singleton 实现的 previous_instances 字典看起来不是线程安全的。如果一个线程正在构造一个对象,而另一个对象检查字典,则存在竞争条件......
R
Ruben Decrop

使用函数属性也很简单

def f():
    if not hasattr(f, 'value'):
        setattr(f, 'value', singletonvalue)
    return f.value

N
Neuron

这是我自己的单例实现。您所要做的就是装饰班级;要获得单例,您必须使用 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)

这不是真正的单身人士。 SingletonList = Singleton(list).Instance(); print(SingletonList is type(SingletonList)()) 应该以真正的单例打印 Truewith your code prints False
@GingerPlusPlus 我知道一些限制,但不是您指出的限制。谢谢你提到它。不幸的是,我目前没有时间细化解决此问题的方法。
根据@GingerPlusPlus 的评论,我给这个答案打了–1。如果您修复它,请告诉我,我将删除 –1。
A
Andrei R.

我更喜欢这个解决方案,我发现它非常清晰明了。例如,如果其他线程已经创建了它,它正在使用双重检查。另外要考虑的是确保反序列化不会创建任何其他实例。 https://gist.github.com/werediver/4396488

import threading


# Based on tornado.ioloop.IOLoop.instance() approach.
# See https://github.com/facebook/tornado
class SingletonMixin(object):
    __singleton_lock = threading.Lock()
    __singleton_instance = None

    @classmethod
    def instance(cls):
        if not cls.__singleton_instance:
            with cls.__singleton_lock:
                if not cls.__singleton_instance:
                    cls.__singleton_instance = cls()
        return cls.__singleton_instance


if __name__ == '__main__':
    class A(SingletonMixin):
        pass

    class B(SingletonMixin):
        pass

    a, a2 = A.instance(), A.instance()
    b, b2 = B.instance(), B.instance()

    assert a is a2
    assert b is b2
    assert a is not b

    print('a:  %s\na2: %s' % (a, a2))
    print('b:  %s\nb2: %s' % (b, b2))

请原谅我的无知,但为什么您需要检查两次 __singleton_instance?你能不能总是拿__singleton_lock然后只检查一次?
正如我之前提到的,我们需要它来确保当我们执行“if”并使用锁时,其他一些线程还没有创建这个实例en.wikipedia.org/wiki/Double-checked_locking这是一个在面试中被问到的相当流行的概念 :)
但是,获得一个无竞争锁的成本肯定是足够低的,如果它很重要,你最好在 C 中实现它? IIRC 获取锁的成本大约是函数调用的一半,因此这里更好的优化可能是避免使用上下文管理器并手动获取锁。如果这是不必要的优化,我认为双重检查更是如此。
双重检查不是一种优化,它是为了确保我们不会创建一个 Singleton 的两个实例。还需要指出的是,这些检查只会在第一次初始化时执行一次。之后它只是返回实例。所以任何优化都是没有意义的。
这是我似乎没有得到的。当然只要你拿着锁检查你就只需要检查一次吗?这就是锁的作用,用于同步访问。
s
socketpair
from functools import cache

@cache
class xxx:
   ....

死容易和工作!


P
Peter Mortensen

方法 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)
mikewatkins.ca 链接(实际上)已损坏。
m
mikenerone

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

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(instance, YourClass) 和类似的操作仍然可以按预期工作(尽管这个位确实需要 abc 所以排除了 Python <2.6)。

调用者仍然只是简单地导入YourClass,它看起来像一个类(因为它是),并且他们正常使用它。无需使调用者适应工厂功能。

YourClass() 实例化的仍然是您实现的 YourClass 的真实实例,而不是任何类型的代理,因此不会产生副作用。

isinstance(instance, YourClass) 和类似的操作仍然可以按预期工作(尽管这个位确实需要 abc 所以排除了 Python <2.6)。

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

我实际上发现更有用的一个相关模式(并不是说我说这些东西是经常需要的)是一种“独特”模式,其中用相同的参数实例化类会导致返回相同的实例。即“每个参数的单例”。以上很好地适应了这一点,变得更加简洁:

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

说了这么多,我确实同意一般建议,如果你认为你需要这些东西之一,你真的应该停下来问问自己是否真的需要。 99% 的时间,YAGNI。


D
Dorcioman

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

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

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


D
DEEPAK SURANA

使用类变量(无装饰器)

通过覆盖 __new__ 方法以返回该类的相同实例。仅第一次初始化类的布尔值:

class SingletonClass:
    _instance = None

    def __new__(cls, *args, **kwargs):
        # If no instance of class already exits
        if cls._instance is None:
            cls._instance = object.__new__(cls)
            cls._instance._initialized = False
        return cls._instance
        
    def __init__(self, *args, **kwargs):
        if self._initialized:
            return

        self.attr1 = args[0]
        # set the attribute to `True` to not initialize again
        self._initialized = True

G
Guard

好吧,除了同意 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 属性的意义何在?据我所知,这没有任何作用?对此我感到很烦,说它不应该表现良好……我将在本周晚些时候进行一些比较测试。
_sealed 确保您的 init 只运行一次;我看不出为什么它的性能应该比常规的类似函数的装饰器更差——该函数每个类只执行一次,并返回一个新的继承类
顺便说一句,您的编辑包含打破缩进的标签您还说“我们正在创建 2 个类”-您的意思是我们正在创建“1 个额外的类”吗?
是的,我的意思是增加一门课。我打算在 __init__ 中包含要在每次初始化时调用的内容。只是一个简单的“在 class.method 中初始化”。至于缩进——你使用了制表符和空格——我修复了大部分,但如果你想得到它似乎错过了一个(只需检查编辑日志)
re init:当然这取决于你,我只是试图模仿其他语言中的单例行为,其中构造函数代码(不完全是 init,但在它的意义上非常接近)只在你想要 init 时被调用一次每次调用,只需终止对 _sealed re 空格/制表符的所有引用 - 好吧,然后我的 emacs 需要修复。反正以上是修正版
T
Tolli

这个怎么样:

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)() 来创建其他实例(这也返回相同的实例)。


您没有定义一个类来将其用作对象。
每次调用 type(MySingleton)() 时,都会调用 MySingleton.__init__() 并多次初始化对象;您可以在 singleton 中写入 cls.__init__ = lambda self: pass 来修复它。此外,覆盖 cls.__call__ 似乎毫无意义,甚至有害 - 在此上下文中定义的 __call__ 在您调用 MySingleton(any, list, of, arguments) 时使用,而不是在您调用 type(MySingleton)(any, list, of, arguments) 时使用。
@GingerPlusPlus,感谢您指出在执行 type(MySingleton)() 时会再次调用 __init__()。您提出的解决方案(添加 cls.__init__ = lambda self: pass)给出了语法错误,因为 lambda 表达式的最后一部分需要是表达式,而不是语句。但是,添加 cls.__init__ = lambda self: None 有效,因此我将其添加到我的答案中。
@GingerPlusPlus,关于 __call__ 的使用。我的意图是让 type(MySingleton)()MySingleton() 返回实例。所以它正在做我想做的事。您可以将 MySingleton 视为单例的类型或单例的实例(或两者兼而有之)。
T
ThorSummoner

这个答案可能不是您想要的。我想要一个单例,因为只有那个对象有它的身份,以便进行比较。在我的例子中,它被用作 Sentinel Value。答案很简单,让任何对象 mything = object() 并且根据 python 的性质,只有那个东西才会有它的身份。

#!python
MyNone = object()  # The singleton

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

我了解到模块实际上可以多次导入,在这种情况下,这只是一个本地单例,在任何能力上都不是真正的单例。
您能否详细说明如何多次导入模块?我见过的唯一一次是在加载模块时发生异常,用户可能稍后仍会加载模块,但副作用已经发生,因此可能会第二次执行某些操作。
一个模块完全加载后,除了强制解释器使用 evalimportlib.reload 之外,我看不到让该模块再次运行的方法。
@sleblanc 我以为我有关于该主题的 SO 帖子,但找不到;这是一个顶级谷歌结果:stackoverflow.com/a/55910681/1695680 IIRC 我需要这个来修补 python 的 stdlib 的信任断言的 ssl 证书链中的一些不正确行为,当以特定方式使用多个域时,允许某些模块将其 ssl 接口替换为monkeypatched 版本和其他版本,并且能够根据需要交换它们。我不推荐猴子补丁,但我很高兴这个选项存在:)
V
Vintage

优点 这是一个真正的类 自动神奇地涵盖继承 将元类用于其正确目的(并让我意识到) 缺点 有吗?

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


B
Boriel

我也更喜欢装饰器语法而不是从元类派生。我的两分钱:

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 失败)

property、classmethod 和 staticmethod 仍将按预期工作

__init__() 构造函数只执行一次

您可以再次使用 @singleton 从您的装饰类继承(没用?)

缺点:

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

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

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


b
byteface

我只是偶然做了一个简单的,并认为我会分享它......

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)

m
mike rodent

它与 fab 的答案略有相似,但不完全相同。

singleton pattern 不要求我们能够多次调用构造函数。作为一个单例应该只创建一次,不应该被视为只创建一次吗? “欺骗”构造函数可以说会损害易读性。

所以我的建议是这样的:

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 时引发异常,并覆盖 __del__() 以引发异常(除非我们知道程序正在结束......)

进一步改进

我感谢那些帮助评论和编辑的人,欢迎更多。虽然我使用 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()

注意事项:

如果你不从 python2.x 中的对象子类化你将得到一个不使用 __new__ 的旧式类 装饰 __new__ 时,你必须使用 @classmethod 进行装饰,否则 __new__ 将是一个未绑定的实例方法 这可能会被改进使用元类,因为这将允许您创建类级属性,可能将其重命名为实例


虽然这是对单例模式的稍微不同的解释,但我很确定它仍然有效,尽管我可能倾向于使用 __new__ 而不是 __init__,因为它纯粹作用于类属性,这会阻止那里短暂地成为第二个例子。这与方法 2 之间的区别在于,尝试多次初始化是返回单个实例还是引发异常。我想我很高兴要么满足单例模式,一个更容易使用,而另一个更明确地说它是一个单例。
显然,在 __init__ 中使用类名会阻止子类化,但这虽然使事情变得更容易,但这不是必需的
谢谢......啊,是的,在抛出异常之前的瞬间第二个实例。我已经修改了 __init__,希望这应该是可子类化的......
酷,the 可能会因为类似的原因而受益于成为类方法
你是对的。然后你可以有一个 SuperElvis 子类单例和(例如)一个 ImaginaryElvis 子类单例......它们可以共存。查看其他想法。请随时改进我的代码。
2
2cynykyl

我不记得我在哪里找到了这个解决方案,但从我的非 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

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


为什么这是一个糟糕的方法?当你想创建另一个单例类时,你必须复制 &粘贴 __new__Don't repeat yourself
另外,为什么你的新人需要 *args**kwargs,然后什么都不做?以这种方式将它们传递到 dict.__new__dict.__new__(cls, *args, **kwargs)
这将在每次调用类时调用 __init__ 方法。如果您的 __init__ 方法确实做了一些事情,您会注意到问题。每当您执行 SomeSingleton() 时,您的单例状态都会由 __init__ 方法重置。
C
Community

基于 Tolli's answer 的代码。

#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)

解释:

创建新类,继承自给定的 cls(它不会修改 cls,以防有人想要例如 singleton(list)) 创建实例。在覆盖 __new__ 之前,这很容易。现在,当我们轻松创建实例时,使用刚才定义的方法覆盖 __new__。该函数仅在调用者期望的情况下返回实例,否则引发 TypeError。当有人试图从装饰类继承时,条件不满足。如果 __new__() 返回 cls 的实例,则新实例的 __init__() 方法将像 __init__(self[, ...]) 一样被调用,其中 self 是新实例,其余参数与传递给的参数相同__新的__()。 instance 已经初始化,所以 function 将 __init__ 替换为 function 什么都不做。

See it working online


S
Serge Rogatch

如果您不需要 Singleton 实例的延迟初始化,那么以下应该是简单且线程安全的:

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

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


b
buhtz

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

使 Foo 的实例随处可访问(全局)。 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!

D
Den-Jason

在为此苦苦挣扎了一段时间后,我最终想出了以下方法,以便从单独的模块调用配置对象时只会加载一次。元类允许将全局类实例存储在内置字典中,目前这似乎是存储适当程序全局的最简洁的方式。

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

o
omerfarukdogan

如果您想使用 instance 作为属性,您可以使用 metaclass。例如;

class SingletonMeta(type):
    def __init__(cls, *args, **kwargs):
        super().__init__(*args, **kwargs)
        cls._instance = None
        cls._locker = threading.Lock()

    @property
    def instance(self, *args, **kwargs):
        if self._instance is None:
            with self._locker:
                if self._instance is None:
                    self._instance = self(*args, **kwargs)
        return self._instance


class MyClass(metaclass=SingletonMeta):
    def __init__(self):
        # init here
        pass


# get the instance
my_class_instance = MyClass.instance

当我们调用 MyClass 两次时会发生什么?我在这里有两个不同的地址......它似乎并没有避免新的实例
p
polvoazul

一个班轮(我并不自豪,但它确实完成了工作):

import sys

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

我已经编辑了答案以避免这个问题
M
Markus Hirsimäki

方法:单次使用后覆盖 __new__

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

优点

极其简单和简洁

真正的类,不需要模块

正确使用 lambda 和 pythonic 猴子补丁

缺点

__new__ 可以再次被覆盖