我有多个类将成为单例(我的用例是记录器的,但这并不重要) 。当我可以简单地继承或修饰时,我不希望在多个类上添加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
Pros
装饰器的添加方式通常比多重继承更直观。
缺点
使用MyClass()创建的对象将是真正的单例对象,MyClass本身是一个函数,而不是类,因此您不能从中调用类方法。也适用于
x = MyClass();
y = MyClass();
t = type(n)();
然后
x == y
但x != t && y != t
方法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__
是否可能被覆盖?方法三:元类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
优点
简单胜于复杂
缺点
没有延迟实例化
#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)
请参阅
if
中的Singleton.__call__
语句。关于元类的几句话。元类是类的类。也就是说,类是其元类的实例。您可以使用
type(obj)
在Python中找到对象的元类。普通的新式类的类型为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
。线程元类的一些(具体)用例是什么?还提供了一些示例,它们通常似乎与声明式编程有关,尤其是在ORM中使用。在这种情况下,如果使用方法#2,并且子类定义了
__new__
方法,则它将在每个调用SubClassOfSingleton()
的时间-因为它负责调用返回存储实例的方法。对于元类,仅在创建唯一实例时才调用一次。您想自定义调用类的含义,这取决于类的类型。通常,使用元类实现单例是有意义的。单例很特殊,因为它仅创建一次,而元类是自定义类创建的方式。如果需要以其他方式自定义单例类定义,使用元类可以为您提供更多控制。
您的单例将不需要多重继承(因为元类不是基类),但是需要创建的子类使用多重继承的类,您需要确保单例类是第一个/最左边的一个具有重新定义
__call__
的元类的类。这不太可能成为问题。实例dict不在实例的名称空间中,因此不会意外覆盖它。您还将听到单例模式违反了“单一责任原则”-每个类只能做一件事。这样,您就不必担心如果需要更改另一代码,便会弄乱代码要做的一件事,因为它们是分开封装的。元类实现通过了此测试。元类负责执行模式,创建的类和子类无需知道它们是单例。方法1未能通过此测试,正如您提到的“ MyClass本身是一个函数,不是类,因此您无法从中调用类方法。”
Python 2和3兼容版本
编写有效的内容在Python2和3中,都需要使用稍微复杂一些的方案。由于元类通常是
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__
实际上是一个静态方法,您必须将该类传递给它,而不是一个类方法,因为尚未创建实际的类何时被调用。所有这些事情对于元类实现也将是正确的。 。方法4比其他装饰器版本要好,但它的代码比单例所需的代码多,而且它的功能尚不清楚。主要问题出在类是它自己的基类上。首先,让一个类成为几乎相同的类的子类,并且名称仅存在于其
__class__
属性中,这不是很奇怪吗?这也意味着您无法定义使用super()
在其基类上调用相同名称的方法的任何方法,因为它们将递归。这意味着您的类无法自定义__new__
,并且不能从需要对其进行调用的任何类中派生。何时使用单例模式
您的用例是想要的更好示例之一使用单例。您在其中一项评论中说:“对我而言,伐木一直是Singletons的自然选择。”你是完全正确的。
当人们说单身人士很糟糕时,最普遍的原因是他们是隐性的共享状态。尽管全局变量和顶级模块导入是显式共享状态,但通常会实例化传递的其他对象。这是一个好点,但有两个例外。
The first, and one that gets mentioned in various places, is when the singletons are constant. Use of global constants, especially enums, is widely accepted, and considered sane because no matter what, none of the users can mess them up for any other user. This is equally true for a constant singleton.
The second exception, which get mentioned less, is the opposite -- when the singleton is only a data sink, not a data source (directly or indirectly). This is why loggers feel like a "natural" use for singletons. As the various users are not changing the loggers in ways other users will care about, there is not really shared state. This negates the primary argument against the singleton pattern, and makes them a reasonable choice because of their ease of use for the task.
Here is a quote from http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html:
Now, there is one kind of Singleton which is OK. That is a singleton where all of the reachable objects are immutable. If all objects are immutable than Singleton has no global state, as everything is constant. But it is so easy to turn this kind of singleton into mutable one, it is very slippery slope. Therefore, I am against these Singletons too, not because they are bad, but because it is very easy for them to go bad. (As a side note Java enumeration are just these kind of singletons. As long as you don't put state into your enumeration you are OK, so please don't.)
另一种半可接受的单例是那些不影响代码执行的单例,它们没有“副作用”。日志记录就是一个很好的例子。它加载了Singletons和全局状态。这是可以接受的(因为它不会对您造成伤害),因为无论是否启用给定的记录器,您的应用程序的行为都没有任何不同。此处的信息以一种方式流动:从您的应用程序进入记录器。甚至认为记录器是全局状态,因为没有信息从记录器流入您的应用程序,所以记录器是可以接受的。如果您想让测试断言某些东西正在被记录,那么您仍然应该注入记录器,但是一般来说,记录器即使处于满状态也不会有害。
评论
不,单身人士永远都不好。日志记录可能是成为全局对象的好人选(虽然如此可怕),但肯定不是单例的。
–Cat Plus Plus
11年7月23日在11:32
Look at googletesting.blogspot.com/2008/08/…. It is generally anti-singleton (for good reason) but it has a good explaination of why immutable singletons and singletons without side effects don't have the same problems, if you're careful. I'm going to quote it a bit at the end of my post.
– agf
Jul 23 '11 at 11:36
我的单例问题是“仅一个实例”的愚蠢前提。这和大量的线程安全问题。和依赖隐藏。全局变量不好,单例只是具有更多问题的全局变量。
–Cat Plus Plus
11年7月23日在11:46
@Cat单身人士有很好的用途。硬件模块的延迟实例化(尤其是在单线程应用程序中)是其中之一(但也存在线程安全的单例)。
– Paul Manta
2011年8月6日下午13:47
元类中的@Alcott __new__是类是新的-定义时,而不是实例是新时。调用类(MyClass())是要覆盖的操作,而不是类的定义。如果您真的想了解Python的工作方式,那么最好的方法(除了继续使用它之外)是docs.python.org/reference/datamodel.html。关于元类的一个很好的参考是eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example。关于单例的好文章是我在此答案中链接的Google博客的系列文章。
–agf
2011-09-20 6:28
#2 楼
class Foo(object):
pass
some_global_variable = Foo()
模块仅导入一次,其他所有内容都过分考虑。不要使用单例,不要使用全局。
评论
你为什么说“不使用单身人士”?任何原因?
–奥尔科特
2011年9月20日在1:30
如果必须腌制单例,这将不起作用。使用您给出的示例:s = some_global_variable; str = pickle.dumps(s); s1 = pickle.loads(str); print s是s1; #错误
–除以零
2012年1月20日11:47
@dividebyzero:is运算符测试指针是否相等。如果pickle.loads返回对先前存在的对象的引用,而不是对新创建的对象的引用,我会感到很惊讶-甚至将其称为bug。因此,测试s是否为s1并不能告诉您使用模块作为单例的适用性。
–乔纳斯·科尔克(JonasKölker)
2014-02-17 10:52
@ leo-the-manic:公平点;但是,这只是Python嵌入对象True,False和None的副作用,与pickle.loads背后的代码无关。同样,只对只读对象也很安全。如果pickle.loads返回对已经存在的可修改对象(例如模块)的引用,那将是一个错误。 (因此,我的意思是dividbyzero的代码示例没有任何内容。)
–乔纳斯·科尔克(JonasKölker)
2014年5月3日,0:18
仅当所有导入都以相同的方式进行时,此方法才有效。 import project.module和import .module将运行两次代码。
– Jan DB
17-10-11在12:19
#3 楼
使用模块。它仅导入一次。在其中定义一些全局变量-它们将是单例的“属性”。添加一些功能-单例的“方法”。评论
因此,您最终得到的是...不是一堂课。您不能将其用作一个类,不能将其作为其他类的基础,使用导入语法,突然之间您将失去OOP的所有优势...
–扫帚的头
2011年7月25日在13:04
如果可以在其上建立其他类,则可能不是单例。您可以创建派生类之一,也可以创建基类之一,但是派生类也是基类的成员,并且您有两个基类,您应该使用哪个?
–SingleNegationElimination
11年7月25日在19:32
@PaulKenjora您的代码中必须有一个错误。如果在模块中定义了全局变量,那么当您从另一个模块访问它时,该变量应具有值。
– warvariuc
17 Mar 30 '17 at 19:40
@theheadofabroom,您可以从base_module导入* ...重新思考,我的朋友!哈哈哈
– polvoazul
18 Mar 7 '18 at 4:46
如何在模块中使用参数初始化单例对象?
–nn0p
19年6月10日在4:51
#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()
其中mysingleton.py是您定义My_Singleton的文件名。之所以起作用,是因为第一次导入文件后,Python不会重新执行代码。
评论
通常是正确的,但有时这还不够。例如。我有一个项目,需要在DEBUG级别记录许多类的实例化。我需要在启动时解析命令行选项,以便在实例化这些类之前设置用户指定的日志记录级别。模块级实例化使该问题变得棘手。我可能会仔细构造应用程序,以便在完成CLI处理之前不会导入所有这些类,但是我的应用程序的自然结构比严格遵循“单核不好”更为重要,因为它们可以做得很干净。
–米克酮
16年8月27日在21:29
如果要在修补my_singleton的同时测试代码,那有可能吗?因为可以在其他模块中实例化此my_singleton。
– Naveen
18年4月24日在3:04
@Naveen-my_singleton是单个对象。如果您“修补”了该更改,则将影响所有以后的引用,即使在其他模块中也是如此。
–艾伦·戴克
18年4月24日在19:30
#5 楼
这是为您提供的一线工具:急于实例化。这可能是您想要的,也可能不是。评论
-1。这是非常糟糕和愚蠢的。您没有定义用作对象的类。您不能再不像type(wat)或wat .__ class__那样丑陋地访问该类。如果您确实想实现此目的,则可以更好地定义该类并立即实例化它,而无需搞砸装饰器。
– 0xc0de
14-10-16在7:56
为什么您需要知道使用单例的类?只需使用单例对象即可。
– Tolli
14-10-17在23:41
它不是单例模式,因此IMO函数的名称应不同。
– GingerPlusPlus
2014年12月30日21:53
维基百科:“单例模式是一种将类的实例化限制为一个对象的设计模式”。我会说我的解决方案就是这样做的。好的,我想一个人可以做wat2 = type(wat)(),但这是python,我们都同意成年人和其他所有东西。您不能保证只会有一个实例,但是可以保证如果人们再提出一个实例,它将看起来很丑陋,并且-如果他们是体面的,正直的人-就像一个警告标志。我想念什么?
–乔纳斯·科尔克(JonasKölker)
15年1月2日,19:35
#6 楼
看看Stack Overflow问题,是否有一种简单,优雅的方法在Python中定义单例?我强烈建议观看Alex Martelli在python中关于设计模式的演讲:第1部分和第2部分。尤其是在第1部分中,他谈论单例/共享状态对象。
评论
尽管这并不是我的问题的真正答案,但是您指向的资源非常有用。我很想给你+1
–扫帚的头
2011年7月25日在13:08
#7 楼
这是我自己的单例实现。您所要做的就是装饰教室。要获取单例,则必须使用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是type(SingletonList)())应该在true单例中打印True;与您的代码打印错误
– GingerPlusPlus
2014年12月30日22:07在
@GingerPlusPlus我知道一些限制,但没有意识到您指出的限制。感谢您提及。不幸的是,我现在没有时间解决这个问题。
– Paul Manta
2014年12月30日22:19在
#8 楼
如果要具有多个相同类的实例,但仅当args或kwargs不同时,才可以使用第三方python软件包Handy Decorators(软件包
decorators
)。 如果您有一个处理
serial
通讯的类,并且要创建一个实例,想要将串行端口作为参数发送,那么采用传统方法将无法正常工作上述装饰器,如果args不同,则可以创建该类的多个实例。对于相同的args,装饰器将返回已经创建的相同实例。
>>> 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
>>>
#9 楼
方法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中都可以运行,则需要执行以下操作:
被“ BaseClass”替换,但我还没有尝试过(我尝试了如图所示的代码)。
评论
当然这不是元类-在python3中,要使用元类来构造MyClass,您可以使用类MyClass(metaclass = Singleton)
–扫帚的头
13年7月30日在14:32
mikewatkins.ca链接已(有效)断开。
– Peter Mortensen
16-3-14在21:54
#10 楼
我将我扔进戒指。这是一个简单的装饰器。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)。实类的静态方法不能通过隐藏它的工厂类来透明地调用。我已经很少使用了它,以至于从未碰到过这种需求,但是通过在工厂中使用实现__getattr__()
的自定义元类来轻松纠正该问题,该类实现了q4312079q,将所有属性的访问权限委派给真实类。 /> 我实际上发现了一个相关的模式更有用(并不是说我经常需要这些东西)是“唯一”模式,其中使用相同的参数实例化该类会导致恢复相同的实例。即“每个论点单身”。上面的代码很好地适应了这个问题,并且变得更加简洁:
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%的时间。
#11 楼
好吧,除了同意关于模块级全局的Pythonic通用建议外,如何处理:评论
_sealed属性的意义是什么?据我所知,这没有任何作用吗?某事让我that不安,这表明它的性能不佳...本周晚些时候,我将进行一些比较测试。
–扫帚的头
11年7月25日在10:32
_sealed确保您的init只运行一次;我看不出为什么它比常规的类似函数的装饰器更糟糕-该函数每个类仅执行一次,并返回一个新的继承类
–守卫
11年7月25日在19:29
顺便说一句,您的编辑包含打破缩进的选项卡您还说“我们正在创建2个类”-您是说我们正在创建“另外1个类”吗?
–守卫
2011年7月25日在19:31
是的,我的意思是多一堂课。我打算在每次初始化时都将其包含在__init__中。只是一个简单的“在class.method中初始化”。至于缩进-您使用了制表符和空格-我已修复了大多数问题,但如果要获取它,似乎已经错过了一个(只需检查编辑日志)
–扫帚的头
2011年7月26日在6:33
重新初始化:当然,这取决于您,我只是尝试模仿其他语言中的单例行为,在这种情况下,如果您希望将初始化设为每次调用时,只需杀死对_sealed re空格/制表符的所有引用-嗯,那么我的emacs需要修复。无论如何,以上是更正的版本
–守卫
2011年7月26日在6:52
#12 楼
怎么样: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年10月16日8:00
每次调用type(MySingleton)()时,都会调用MySingleton .__ init __(),并且该对象将多次初始化。您可以用cls .__ init__ = lambda self修复它:传入您的单例。另外,覆盖cls .__ call__似乎毫无意义,甚至有害-在调用MySingleton(any,list,of,arguments)时使用此上下文中定义的__call__,而不是在调用type(MySingleton)(any,list,of,arguments时) )。
– GingerPlusPlus
2014年12月31日13:23
@GingerPlusPlus,感谢您指出在执行type(MySingleton)()时再次调用__init __()。您建议的解决方案(添加cls .__ init__ = lambda self:pass)会产生语法错误,因为lambda表达式的最后一部分需要是表达式,而不是语句。但是,添加cls .__ init__ = lambda self:没有效果,因此我将其添加到了答案中。
– Tolli
2015年1月2日,下午5:42
@GingerPlusPlus,关于__call__的使用。我的目的是使type(MySingleton)()和MySingleton()都返回实例。所以它正在做我想要的。您可以将MySingleton视为单例的类型或单例的实例(或两者)。
– Tolli
2015年1月2日,下午5:43
#13 楼
使用函数属性也非常简单def f():
if not hasattr(f, 'value'):
setattr(f, 'value', singletonvalue)
return f.value
#14 楼
基于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)
解释:
创建新类,继承给定的
cls
(如果有人想要
cls
,它不会修改singleton(list)
)创建实例。
现在,当我们轻松创建实例后,使用刚才定义的方法覆盖
__new__
。 该函数仅在调用者期望的情况下返回
__new__
,否则引发instance
。当有人尝试从修饰的类继承时,不满足条件。
如果
TypeError
返回__new__()
的实例,则将像cls
一样调用新实例的__init__()
方法,其中self是新实例,其余参数与传递给__init__(self[, ...])
的参数相同。__new__()
已被初始化,因此该函数将instance
替换为不执行任何操作的函数。#15 楼
它与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__()
以便引发异常(除非我们知道程序即将结束...)进一步的改进
我的谢谢那些对注释和编辑有所帮助的人,欢迎其中的更多。当我使用Jython时,这应该可以更普遍地工作并且是线程安全的。如果您不从python2.x中的对象子类化,您将获得一个不使用
__new__
的旧类。 method 可以通过使用元类来改进,因为这可以使
__new__
成为类级别的属性,并可能将其重命名为__new__
评论
尽管这与单例模式的解释稍有不同,但我可以肯定它仍然有效,尽管我可能会尝试使用__new__而不是__init__,因为它纯粹作用于类属性,因此可以防止短暂地成为第二个实例。此方法与方法2之间的区别在于,尝试多次初始化还是返回单个实例还是引发异常。我想我很高兴能满足单例模式,一个更易于使用,而另一个更明确地表明它是单例。
–扫帚的头
16-2-21在9:40
显然,在__init__中使用类名可以防止子类化,但这虽然使事情变得容易,但这不是必需的
–扫帚的头
16-2-21在9:43
谢谢...啊,是的,在抛出异常之前的第二个瞬间。我已经修改了__init__,因此希望它应该是可子类化的...
–麦克·啮齿动物
16-2-21在9:51
太酷了,由于类似的原因,可以从类方法中受益
–扫帚的头
16-2-21在10:32
你是对的。然后,您可以拥有一个SuperElvis子类单例和(例如)一个ImaginaryElvis子类单例……它们可以共存。查看其他想法。请随时改进我的代码。
–麦克·啮齿动物
16-2-21在11:35
#16 楼
一支班轮(我不为此感到自豪,但确实能胜任):class Myclass:
def __init__(self):
# do your stuff
globals()[type(self).__name__] = lambda: self # singletonify
评论
只要您的班级尚未导入任何其他模块中,这就可以完成工作...
–阿兰·费
18-10-24在12:41
真正。如果您可以支付初始化类的费用,则可以在类定义之后立即运行Myclass()来避免这种情况。
– polvoazul
18-10-26在14:56
#17 楼
如果不需要懒惰地初始化Singleton实例,则以下操作应该很容易且具有线程安全性:在模块导入时。
#18 楼
也许我误会了单例模式,但是我的解决方案是这个简单而实用的(pythonic?)。这段代码实现了两个目标使
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!
#19 楼
经过一段时间的努力,我最终想到了以下内容,以便从单独的模块中调用配置对象时,它只会被加载一次。元类允许将全局类实例存储在内置字典中,这在目前看来是存储适当程序全局的最简洁的方法。#20 楼
我将推荐一种使用元类的优雅解决方案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
从输出中可以看到,仅实例化了一个对象
#21 楼
我更喜欢这种解决方法,我发现它非常清楚和直接。例如,如果已经创建了其他一些线程,则使用双重检查。其他任何实例。
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,然后只检查一次吗?
–扫帚的头
10月26日22:21
如前所述,我们需要确保执行“ if”并使用锁时,其他一些线程尚未创建该实例。wikipedia.org/wiki/Double-checked_locking面试时要问的概念:)
– Andrei R.
10月26日22:29
但是,毫无疑问,获取无争议的锁的成本足够低,以至于如果它很重要,那么最好在C中实现它? IIRC获取锁的成本约为函数调用成本的一半,因此,更好的优化方法可能是避免使用上下文管理器并手动获取锁。如果这是不必要的优化,我认为再次检查甚至更是如此。
–扫帚的头
10月26日23:54
双重检查不是一种优化,而是要确保我们不创建两个Singleton实例。还要指出的是,这些检查在第一次初始化时将只执行一次。之后,它只是返回实例。因此,任何优化都是毫无意义的。
– Andrei R.
10月27日22:04
这是我似乎没有得到的。当然,只要您拿着锁进行检查,就只需要检查一次?这就是锁的用途,用于同步访问。
–扫帚的头
10月27日22:08
#22 楼
我也更喜欢装饰器语法而不是从元类派生。我的两分钱: 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失败)仅执行一次您可以再次使用@singleton从修饰的类继承(没用吗?)
od
property
。如果仍然需要此方法,我建议使用上面建议的元类。 )。最后,按照其他建议,考虑在python中使用模块。模块是对象。您甚至可以将它们传递给变量,然后将其注入其他类。
#23 楼
我不记得在哪里找到该解决方案,但是从我的非Python专家的角度来看,它是最“优雅”的:我为什么喜欢这个?没有装饰器,没有元类,没有多重继承...,如果您决定不再希望它成为Singleton,只需删除
__new__
方法。因为我是Python(和OOP的新手)的新手,所以我希望有人会直接告诉我为什么这是一种糟糕的方法?评论
为什么这是一个可怕的方法?当您要创建另一个单例类时,必须复制并粘贴__new__。不要重复自己。
– GingerPlusPlus
2014年12月30日17:56
另外,为什么您的新手需要* args和** kwargs,然后对它们却什么也不做呢?这样将它们传递给dict .__ new__:dict .__ new __(cls,* args,** kwargs)。
– GingerPlusPlus
2014年12月31日13:29在
每次调用该类时,都会调用__init__方法。如果您的__init__方法实际上做了一些事情,您会发现问题所在。每当执行SomeSingleton()时,__ init__方法都会重置您的单身状态。
–阿兰·费
18-10-24在12:45
#24 楼
这个答案可能不是您想要的。我想要一个单例,因为只有那个对象才具有其身份,以便进行比较。就我而言,它被用作前哨值。答案很简单,根据python的性质,使任何对象mything = object()
都只有该东西才具有其标识。评论
我了解到,模块实际上可以多次导入,在这种情况下,这只是一个本地单例,实际上并不是任何容量的单例。
–雷神召唤师
18年3月23日20:00
您能否详细说明如何多次导入模块?我唯一看到的是在加载模块时发生异常,用户仍然可以稍后加载模块,但是副作用已经发生,因此某些动作可能会第二次执行。
–sleblanc
19年3月16日在15:25
模块完全加载后,除了通过使用eval或importlib.reload强制解释器执行该模块外,我看不到其他方法可以再次运行该模块。
–sleblanc
19年3月16日在15:29
@sleblanc我以为我有关于该主题的帖子,找不到它;这是谷歌的最佳搜索结果:stackoverflow.com/a/55910681/1695680 IIRC我需要用它来修补以特定方式使用多个域时python stdlib的ssl证书信任链中的某些错误行为,从而允许某些模块它们的ssl接口已替换为monkeypatched版本,而其他则未替换,并且能够根据需要交换它们。我不建议猴子打补丁,但是我很高兴这个选项存在:)
–雷神召唤师
11月1日20:15
#25 楼
这是实现单例的首选方式: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
评论
严格来说,这不是单例,因为它允许存在多个类的实例。一种改进是使类无法初始化,并使所有类方法作用于类属性
–扫帚的头
2014年10月30日在8:21
#26 楼
这种解决方案在模块级别上导致了一些名称空间污染(三个定义,而不仅仅是一个定义),但是我发现它很容易遵循。 ),但不幸的是,类在其自身的定义中不可用。# 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
由于不可能,我们可以将初始化和静态实例分解开in
急于初始化:
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
评论
另三种技术:代替模块(通常-通常,我认为-这是更适合Python的模式,但是在某种程度上取决于您对它的处理方式);创建一个实例并处理它(foo.x或如果您坚持使用Foo.x代替Foo()。x);使用类属性和静态/类方法(Foo.x)。@ChrisMorgan:如果您只打算使用类/静态方法,那么就不用费心创建类了。
@Cat:效果是相似的,但是创建全局变量的原因几乎可以是任何事情,包括不知道任何更好的事情。为什么要创建一个单例?如果您不得不问您不应该在这里。这种明确性不仅更具pythonic性,而且使维护变得更加简单。是的,单例对于全局变量是语法糖,但是对于一大堆难看的东西,类则是语法糖,我认为没有人会告诉你,没有它们总是更好。
@BiggAl:无论您如何实现,单例都不是Pythonic。它们充其量是一个有缺陷的设计的标志。
反信号情绪是最糟糕的货运崇拜节目。与听到的人一样(很少为实际阅读而烦恼)“ Goto声明被认为是有害的”,并且认为gotos是不正确的代码的标志,无论上下文如何。