这个问题不是为了讨论是否需要单例设计模式,是否是反模式,还是针对任何宗教战争,而是要讨论如何以最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
优点
使用元类
我建议使用方法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)。普通的新式类是类型type。Logger上面代码中的将会是type class ‘your_module.Singleton’,就像的(唯一的)实例Logger将是type一样class ‘your_module.Logger’。当你调用记录仪与Logger(),Python的首先要求的元类Logger,Singleton,做什么,允许实例创建要捷足先登。此过程与Python在通过class __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和全局状态。这是可以接受的(因为它不会对你造成伤害),因为无论是否启用给定的记录器,你的应用程序的行为都没有任何不同。此处的信息以一种方式流动:从你的应用程序进入记录器。甚至认为记录器是全局状态,因为没有信息从记录器流入你的应用程序,所以记录器是可以接受的。如果你想让测试断言某些东西正在被记录,那么你仍然应该注入记录器,但是总的来说,记录器即使处于满状态也不会有害。
问题内容: 我正在寻找一种在python中动态创建html文件的方法。我正在编写画廊脚本,该脚本在目录中进行迭代,收集文件元数据。然后,我打算使用此数据基于html自动创建图片库。事情很简单,只是一张图片表。 我真的不认为手动写入文件是最好的方法,并且代码可能很长。那么,有没有更好的方法可以做到这一点,可能是html特定的? 问题答案: 我认为,如果我对您的理解正确,那么您可以在此处看到“使用Py
问题内容: AFAIK,Python中没有curses菜单扩展,因此您必须推出自己的解决方案。我知道这个补丁http://bugs.python.org/issue1723038,但我不知道它的当前状态是什么。我在http://www.promisc.org/blog/?p=33上找到了一个很好的Python类,用于包装我想要的“ cmenu”,但我对此也有疑问。我想制作一个菜单,用户可以选择一个
问题内容: 我有一个日志文件,其中使用python记录了一些测试命令及其状态(通过/失败)。现在,我希望测试命令不应写为简单文本,而应写为超链接。这样,当我单击它们时,将打开另一个链接到它们的文件。 例如: 现在,我希望写在logfile.log中的CommandName应该是文件TestCommand.log的超链接,以便当我单击CommandName时,文件TestCommand.log会打开
问题内容: 我想创建一种秒表,当分钟数达到20分钟时,会弹出一个对话框,该对话框不是问题。但是我的分钟变量在此代码中没有增加。 问题答案: 您可以使用以下方法真正简化整个程序:
我对tries和DAWGs(直接无环字图)很感兴趣,我已经读了很多关于它们的东西,但我不明白输出trie或DAWG文件应该是什么样子。 null 我也会很感激一个DAWG和Trie的输出。 我不想看到带有相互链接的气泡的图形表示,我想知道一旦一组单词被转换为try或dawgs后的输出对象。
问题内容: 我知道如何快速创建单例类。创建单例类的最佳简便方法如下: 但是我不需要任何普通班的单身人士。我需要为viewcontroller类创建单例。所以我用这段代码创建单例 它给我附近的错误 似乎要我使用进行初始化。但是我应该通过什么参数或值? 问题答案: 如果您真的想为某个场景对应的视图控制器设置单例,则可能需要执行以下操作: 在此示例中,情节提要是,问题场景的情节提要标识符是。显然,将这