在Python中创建单例
这个问题不是为了讨论是否需要单例设计模式,是否是反模式,还是针对任何宗教战争,而是要讨论如何以最pythonic的方式在Python中最好地实现此模式。在这种情况下,我定义“最pythonic”表示它遵循“最小惊讶原则”。
我有多个将成为单例的类(我的用例用于记录器,但这并不重要)。当我可以简单地继承或修饰时,我不希望增加gumph来使几个类杂乱无章。
最佳方法:
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
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__
。
模块文件 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 & 3class _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中创建单例 的全部内容, 来源链接: utcz.com/qa/408655.html