在 Python 中创建单例
Posted
技术标签:
【中文标题】在 Python 中创建单例【英文标题】:Creating a singleton in Python 【发布时间】:2011-10-09 07:05:01 【问题描述】:这个问题不是为了讨论singleton design pattern 是否可取、是否是反模式或任何宗教战争,而是讨论如何在 Python 中最好地实现这种模式这是最pythonic的方式。在这种情况下,我将“最 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 = type(n)();
然后是x == y
但x != 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__
可以在从第二个基类继承期间被覆盖?人们必须考虑的不仅仅是必要的。
方法三:metaclass
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:如果你打算只使用类/静态方法,那么真的不用费心创建一个类。
***.com/questions/42558/… 和 ***.com/questions/31875/… 的副本。
@Cat:效果类似,但是创建全局变量背后的原因几乎可以是任何事情,包括不知道更好。为什么要创建一个单例?如果你不得不问你不应该在这里。这种明确性不仅更加 Pythonic,而且使维护更加简单。是的,单例是全局变量的语法糖,但是类是一大堆难看的东西的语法糖,我认为没有人会告诉你,没有它们你总是会更好。
反信号灯的情绪是最糟糕的货运***节目。与人们听到(很少有人真正阅读)“Goto 语句被认为有害”并认为 goto 是错误代码的标志(无论上下文如何)的情况相同。
【参考方案1】:
使用元类
我会推荐方法#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
语句。
关于元类的几句话。元类是类的类;也就是说,类是其元类的实例。您可以在 Python 中使用type(obj)
找到对象的元类。普通的新式类是type
类型。上面代码中的Logger
将是class 'your_module.Singleton'
类型,就像Logger
的(唯一)实例将是class 'your_module.Logger'
类型一样。当你用Logger()
调用logger时,Python首先询问Logger
、Singleton
的元类,做什么,允许实例创建被抢占。此过程与 Python 通过调用 __getattr__
询问类的操作相同,当您通过执行 myclass.attribute
引用其中一个属性时。
元类本质上决定了类的定义意味着什么以及如何实现该定义。参见例如http://code.activestate.com/recipes/498149/,它基本上使用元类在Python 中重新创建了C 风格的struct
s。线程What are some (concrete) use-cases for metaclasses? 也提供了一些示例,它们通常似乎与声明式编程有关,尤其是在 ORM 中使用时。
在这种情况下,如果你使用你的方法#2,并且一个子类定义了一个__new__
方法,它将在你每次调用SubClassOfSingleton()
时执行 -- 因为它负责调用返回存储实例的方法。使用元类,它只会被调用一次,当唯一的实例被创建时。您想自定义调用类的含义,这由它的类型决定。
一般来说,使用元类实现单例有意义。单例之所以特殊,是因为只创建一次,而元类是您自定义类的创建的方式。如果您需要以其他方式自定义单例类定义,使用元类可为您提供更多控制权。
您的单例不需要多重继承(因为元类不是基类),但是对于使用多重继承的已创建类的子类,您需要确保单例类是第一个/最左边具有重新定义__call__
的元类的类,这不太可能成为问题。实例字典不在实例的命名空间中,因此不会意外覆盖它。
您还会听说单例模式违反了“单一职责原则”——每个类应该只做一件事。这样一来,如果您需要更改另一件事,您就不必担心会弄乱代码所做的一件事,因为它们是独立的且被封装的。元类实现通过了这个测试。元类负责强制执行模式,并且创建的类和子类不需要知道它们是单例。 方法 #1 未能通过此测试,正如您所指出的“MyClass 本身是一个函数,而不是一个类,因此您不能从中调用类方法。”
Python 2 和 3 兼容版本
编写适用于 Python2 和 3 的内容需要使用稍微复杂的方案。由于元类通常是 type
类型的子类,因此可以使用一个在运行时动态创建一个中间基类,并将其作为元类,然后使用 that 作为公共 @987654349 的基类@基类。解释起来比做起来难,如下图所示:
# 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__
的任何类派生。
何时使用单例模式
您的用例是想要使用单例的更好的示例之一。您在其中一个 cmets 中说:“对我来说,伐木似乎一直是单身人士的自然候选者。”你完全正确。
当人们说单例不好时,最常见的原因是它们是隐式共享状态。虽然全局变量和***模块导入是显式共享状态,但传递的其他对象通常是实例化的。这是一个很好的观点,有两个例外。
第一个,也是在很多地方都提到过的,是单例常量。全局常量的使用,尤其是枚举,被广泛接受,并且被认为是理智的,因为无论如何,没有一个用户可以为任何其他用户搞砸它们。这同样适用于常量单例。
较少提及的第二个例外情况正好相反——当单例只是一个数据接收器,而不是一个数据源(直接或间接)。这就是为什么记录器感觉像是单例的“自然”用途。由于各种用户不会以其他用户关心的方式更改记录器,因此存在没有真正共享的状态。这否定了反对单例模式的主要论点,并使它们成为一个合理的选择,因为它们易于使用完成任务。
这是来自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。一篇关于单例的好文章是我在这个答案中链接的谷歌博客的系列。【参考方案2】:
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 对 True
、False
和 None
对象进行实习的副作用,与 pickle.loads
背后的代码无关。此外,只对只读对象进行操作是安全的。如果pickle.loads
要返回对已经存在的modifiable 对象(例如模块)的引用,那将是一个错误。 (所以我坚持我的暗示,即 dividebyzero 的代码示例并不能证明任何事情。)
这仅在所有导入以相同方式发生时才有效。 import project.module
和 import .module
将运行代码两次。【参考方案3】:
使用模块。它只导入一次。在其中定义一些全局变量——它们将是单例的“属性”。添加一些函数 - 单例的“方法”。
【讨论】:
所以你最终得到的是......不是一个类。你不能把它当作一个类来使用,你不能把其他类作为它的基础,你使用 import 语法,突然之间你就失去了 OOP 的所有好处...... 如果你可以基于它来创建其他类,那么它可能就不是单例了。您可以创建一个派生类,也可以创建一个基类,但是派生类也是基类的成员,并且您有两个基类,您应该使用哪一个? @PaulKenjora 您的代码中一定有错误。如果你在一个模块中定义了一个全局变量,当你从另一个模块访问它时,它应该有值。 @theheadofabroom 你可以import * from base_module
... 重新考虑 OOP 我的朋友!哈哈哈
如何在模块中初始化带有参数的单例对象?【参考方案4】:
您可能永远不需要 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 My_Singleton(object):
def foo(self):
pass
my_singleton = My_Singleton()
使用方法:
from mysingleton import my_singleton
my_singleton.foo()
其中 mysingleton.py 是定义 My_Singleton 的文件名。这是因为第一次导入文件后,Python 不会重新执行代码。
【讨论】:
大部分是正确的,但有时这还不够。例如。我有一个项目需要在调试级别记录许多类的实例化。我需要在启动时解析命令行选项,以便在实例化这些类之前设置用户指定的日志记录级别。模块级实例化使这成为问题。有可能我可以仔细构建应用程序,以便在 CLI 处理完成之前不会导入所有这些类,但我的应用程序的自然结构比教条地坚持“单例不好”更重要,因为它们可以做得很干净。 如果您在修补 my_singleton 的同时测试您的代码,这可能吗?因为这个 my_singleton 可以在其他模块中实例化。 @Naveen - my_singleton 是单个对象。如果您“修补”它,那么更改将影响所有未来的引用,即使在其他模块中也是如此。 这在某些情况下可能有效,但有时延迟初始化很重要。在我的用例中,初始化需要 400 毫秒,所以我不想仅仅因为我导入了模块而发生这种情况。只有在真正需要单例时才需要这样做。 @joanis。同意对于每个可能的用例都没有完美的解决方案。也许,您仍然可以对代码中耗时的部分使用延迟初始化,而不是将其放入构造函数中。或者您可能需要此页面上的其他更复杂的建议之一。【参考方案5】:这是一个适合你的单行:
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 的模块作为单例,这实际上是一种零线解决方案。【参考方案6】:
查看 Stack Overflow 问题 Is there a simple, elegant way to define singletons in Python? 并提供多种解决方案。
我强烈推荐观看 Alex Martelli 关于 Python 设计模式的演讲:part 1 和 part 2。特别是,在第 1 部分中,他谈到了单例/共享状态对象。
【讨论】:
虽然这不是我问题的真正答案,但您指向的资源非常有用。我不情愿地给你一个 +1【参考方案7】: 如果想拥有多个相同类的实例,但前提是args或kwargs不同,可以使用第三方python包Handy Decorators(包decorators
)。
例如。
-
如果您有一个处理
serial
通信的类,并且要创建一个您希望将串行端口作为参数发送的实例,那么使用传统方法将行不通
使用上述装饰器,如果参数不同,可以创建类的多个实例。
对于相同的参数,装饰器将返回已创建的相同实例。
>>> 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
字典看起来不是线程安全的。如果一个线程正在构造一个对象,而另一个对象检查字典,则存在竞争条件......【参考方案8】:
使用函数属性也很简单
def f():
if not hasattr(f, 'value'):
setattr(f, 'value', singletonvalue)
return f.value
【讨论】:
【参考方案9】:这是我自己的单例实现。您所要做的就是装饰班级;要获得单例,您必须使用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)())
应该在真正的单例中打印 True
; with your code prints False
@GingerPlusPlus 我知道一些限制,但不是你指出的那个。谢谢你提到它。不幸的是,我现在没有时间细细想办法解决这个问题。
根据@GingerPlusPlus 的评论,我给出的答案是–1。如果你修复它,请告诉我,我会删除 -1。【参考方案10】:
我更喜欢这个解决方案,我发现它非常清晰明了。 例如,如果其他线程已经创建了它,它正在使用双重检查。 另外要考虑的是确保反序列化不会创建任何其他实例。 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 获取锁的成本大约是函数调用的一半,因此这里更好的优化可能是避免使用上下文管理器并手动获取锁。如果这是不必要的优化,我认为双重检查更是如此。
双重检查不是优化,它是为了确保我们不会创建两个单例实例。还需要指出的是,这些检查只会在第一次初始化时执行一次。之后它只是返回实例。所以任何优化都是没有意义的。
这似乎是我没有得到的。当然只要你拿着锁检查你就只需要检查一次吗?这就是锁的作用,用于同步访问。【参考方案11】:
方法 3 看起来很简洁,但是如果你想让你的程序在Python 2 和Python 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
我认为作业中的“object”需要替换为“BaseClass”,但我没有尝试过(我已经尝试过如图所示的代码)。
【讨论】:
这肯定不是元类 - 在 python3 中使用元类来构造 MyClass 你会这样做class MyClass(metaclass=Singleton)
mikewatkins.ca 链接(实际上)已损坏。【参考方案12】:
我会把我的扔进擂台。这是一个简单的装饰器。
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
我确实想到了一个缺点:真实类的类方法和静态方法不能通过隐藏它的工厂类透明地调用。我很少使用这个,以至于我从来没有遇到过这种需要,但是通过在实现__getattr__()
的工厂上使用自定义元类来将所有ish 属性访问委托给真正的类,可以很容易地纠正它。
我实际上发现更有用的一个相关模式(并不是我说这些东西经常需要)是一个“唯一”模式,其中使用 相同的参数实例化类 em> 导致返回相同的实例。 IE。 “每个参数的单例”。以上很好地适应了这一点,变得更加简洁:
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。
【讨论】:
【参考方案13】:我会推荐一个使用元类的优雅解决方案
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
从输出中可以看出,只实例化了一个对象
【讨论】:
【参考方案14】:好吧,除了同意 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 需要定影。反正以上是修正版【参考方案15】:
这个怎么样:
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,感谢您指出__init__()
在执行type(MySingleton)()
时再次被调用。您提出的解决方案(添加cls.__init__ = lambda self: pass
)给出了语法错误,因为 lambda 表达式的最后一部分需要是表达式,而不是语句。但是,添加 cls.__init__ = lambda self: None
有效,所以我将其添加到我的答案中。
@GingerPlusPlus,关于__call__
的使用。我的意图是让type(MySingleton)()
和MySingleton()
返回实例。所以它正在做我想做的事。您可以将 MySingleton 视为单例的类型或单例的实例(或两者兼而有之)。【参考方案16】:
这与fab的答案略有相似,但不完全相同。
单例合约不要求我们能够多次调用构造函数。作为一个单例应该只创建一次,不应该被视为只创建一次吗? “欺骗”构造函数可能会损害可读性。
所以我的建议是这样的:
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__()
以引发异常(除非我们知道程序正在结束.. .)
进一步改进
感谢那些在 cmets 和编辑方面提供帮助的人,欢迎更多。虽然我使用 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__
将是未绑定的实例方法
这可能会通过使用元类来改进,因为这将允许您将the
设置为类级属性,可能将其重命名为instance
【讨论】:
虽然这对单例模式的解释略有不同,但我很确定它仍然有效,尽管我可能会倾向于使用__new
__ 而不是 __init__
,因为它纯粹是在起作用在类属性上,这可以防止那里短暂地成为第二个实例。这与方法 2 之间的区别在于,尝试多次初始化是返回单个实例还是引发异常。我想我很高兴要么满足单例模式,一个更容易使用,而另一个更明确地说它是一个单例。
显然在__init__
中使用类名会阻止子类化,但这虽然使事情变得更容易,但这不是必需的
谢谢...啊,是的,在抛出异常之前的瞬间第二个实例。我已经修改了__init__
,希望这应该是可子类化的......
酷,the
可能会因为类似的原因而受益于成为类方法
是的,你是对的。然后你可以有一个 SuperElvis 子类单例和(例如)一个 ImaginaryElvis 子类单例......它们可以共存。查看其他想法。请随时改进我的代码。【参考方案17】:
优点
这是一个真正的类自动神奇地覆盖继承使用元类 出于正确的目的(并让我意识到)缺点
有吗?
这将是序列化的问题。如果您尝试从文件(pickle)反序列化对象,它将不会使用__call__
,因此它将创建新文件,您可以使用带有__new__
的基类继承来防止这种情况发生。
【讨论】:
【参考方案18】:我也更喜欢装饰器语法而不是从元类派生。我的两分钱:
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 中使用模块。模块是对象。您甚至可以将它们传递给变量并将它们注入到其他类中。
【讨论】:
【参考方案19】:我只是偶然做了一个简单的,并认为我会分享它......
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)
【讨论】:
【参考方案20】:使用类变量(无装饰器)
通过重写__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
【讨论】:
【参考方案21】:代码基于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__
。
该函数仅在调用者期望的情况下返回instance
,否则引发TypeError
。
当有人试图从装饰类继承时,条件不满足。
如果
__new__()
返回cls
的实例,则将调用新实例的__init__()
方法,如__init__(self[, ...])
,其中self 是新实例,其余参数是与传递给__new__()
的相同。
instance
已经初始化,所以函数将 __init__
替换为什么都不做的函数。
See it working online
【讨论】:
【参考方案22】:如果您不需要对 Singleton 的实例进行延迟初始化,那么以下应该是简单且线程安全的:
class A:
instance = None
# Methods and variables of the class/object A follow
A.instance = A()
这样A
是在模块导入时初始化的单例。
【讨论】:
【参考方案23】:也许我对单例模式有误解,但我的解决方案就是这么简单实用(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!
【讨论】:
【参考方案24】:在为此苦苦挣扎了一段时间后,我最终想出了以下方法,以便从单独的模块调用配置对象时只会加载一次。元类允许将全局类实例存储在 builtins 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>...
【讨论】:
【参考方案25】:如果您想使用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
时会发生什么?我这里有两个不同的地址......它似乎并没有避免新的实例【参考方案26】:
一个班轮(我并不自豪,但它确实能胜任):
import sys
class Myclass:
def __init__(self):
# do your stuff
vars(sys.modules[__name__])[type(self).__name__] = lambda: self # singletonify
【讨论】:
我已经编辑了答案以避免这个问题【参考方案27】:我不记得在哪里找到了这个解决方案,但从我非 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__
方法重置。【参考方案28】:
这个答案可能不是您想要的。我想要一个单例,因为只有那个对象有它的身份,以便进行比较。就我而言,它被用作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
【讨论】:
我了解到模块实际上可以多次导入,在这种情况下,这只是一个本地单例,实际上并不是任何容量的单例。 你能详细说明一个模块是如何被多次导入的吗?我见过的唯一一次是在加载模块时发生异常,用户可能稍后仍会加载模块,但副作用已经发生,因此可能会第二次执行某些操作。 一个模块完全加载后,除了强制解释器使用eval
或importlib.reload
之外,我看不到让该模块再次运行的方法。跨度>
@sleblanc 我以为我有关于该主题的 SO 帖子,但找不到;这是一个***谷歌结果:***.com/a/55910681/1695680IIRC 当以特定方式使用多个域时,我需要这个来修补 python 的 stdlib 的信任断言的 ssl 证书链中的一些不正确行为,允许某些模块将其 ssl 接口替换为monkeypatched 版本和其他版本,并且能够根据需要交换它们。我不推荐猴子补丁,但我很高兴这个选项存在:)【参考方案29】:
from functools import cache
@cache
class xxx:
....
简单易用!
【讨论】:
【参考方案30】:这是结合@agf 和@(Siddhesh Suhas Sathe) 解决方案的简单实现,其中它使用元类并考虑构造函数参数,因此如果您使用完全相同的参数创建foo
类,则可以返回相同的实例
class SingletonMeta(type):
_instances =
def __call__(cls, *args, **kwargs):
"""
Possible changes to the value of the `__init__` argument do not affect
the returned instance.
"""
cls_instances = cls._instances.get(cls) or []
matching_instances = list(
filter(
lambda x: x["args"] == args and x["kwargs"] == kwargs,
cls_instances,
)
)
if len(matching_instances) == 1:
return matching_instances[0]["instance"]
else:
instance = super().__call__(*args, **kwargs)
cls_instances.append("instance": instance, "args": args, "kwargs": kwargs)
cls._instances[cls] = cls_instances
return instance
class foo(metaclass=SingletonMeta):
def __init__(self, param, k_param=None) -> None:
print("Creating new instance")
self.param = param
self.k_param = k_param
self._creation_time = time.time()
【讨论】:
以上是关于在 Python 中创建单例的主要内容,如果未能解决你的问题,请参考以下文章