_这个问题不是为了讨论 单例设计模式 是否可取、是否是反模式或任何宗教战争,而是讨论如何以最 Pythonic 的方式在 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 = 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__
可以在从第二个基类继承期间被覆盖?一个人必须考虑比必要的更多。
方法 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
优点
- 简单胜于复杂
缺点
- 没有延迟实例化
原文由 theheadofabroom 发布,翻译遵循 CC BY-SA 4.0 许可协议
使用元类
我会推荐 Method #2 ,但你最好使用 元类 而不是基类。这是一个示例实现:
或者在 Python3 中
如果你想在每次调用类时运行
__init__
,添加到 --- 中的
Singleton.__call__
if
语句。关于元类的几句话。元类是 类的类;也就是说,一个类是 它的元类的一个实例。您可以使用
type(obj)
在 Python 中找到对象的元类。普通的新式类是type
类型。Logger
in the code above will be of typeclass 'your_module.Singleton'
, just as the (only) instance ofLogger
will be of typeclass 'your_module.Logger'
.当你用Logger()
调用 logger 时,Python 首先询问 — 的元类,Logger
Singleton
,做什么,允许实例创建被抢占。这个过程与 Python 通过调用__getattr__
询问类做什么是相同的,当您通过执行myclass.attribute
引用它的一个属性时。元类本质上决定 了类的定义意味着什么 以及如何实现该定义。参见例如 http://code.activestate.com/recipes/498149/ ,它基本上使用元类在 Python 中重新创建 C 风格
struct
s。线程 元类有哪些(具体)用例? 还提供了一些示例,它们通常似乎与声明式编程有关,尤其是在 ORM 中使用时。在这种情况下,如果您使用 Method #2 ,并且子类定义了
__new__
方法,那么 每次 调用SubClassOfSingleton()
时都会执行该方法,因为它负责调用返回存储实例的方法。使用元类,它只会在创建唯一实例时 被调用一次。您想 自定义调用 class 的含义,这由它的类型决定。一般来说,使用元类来实现单例 是有意义 的。单例是特殊的,因为 它只创建一次,而元类是您自定义 创建类 的方式。如果您需要以其他方式自定义单例类定义,则使用元类可为您提供 更多控制。
您的单例 不需要多重继承(因为元类不是基类),但是对于使用多重继承 的已创建类 的子类,您需要确保单例类是第 一个/最左边 的具有重新定义的元类
__call__
这不太可能成为问题。实例字典 不在实例的命名空间中,因此它不会意外覆盖它。您还会听说单例模式违反了“单一职责原则”——每个类应该 只做一件事。这样一来,如果您需要更改另一件事,您就不必担心会弄乱代码所做的一件事,因为它们是独立的且被封装的。元类实现 通过了这个测试。元类负责 执行模式,创建的类和子类不需要 知道它们是单例。 方法 #1 未能通过此测试,正如您在“MyClass 本身是一个函数,而不是类,因此您不能从中调用类方法”中所指出的那样。
Python 2 和 3 兼容版本
编写适用于 Python2 和 3 的东西需要使用稍微复杂的方案。由于元类通常是
type
类型的子类,因此可以使用一个在运行时动态创建中间基类,并将其作为元类,然后将 其 用作公共的基类Singleton
基类。解释起来比做起来难,如下图所示:这种方法具有讽刺意味的是,它使用子类化来实现元类。一个可能的优势是,与纯元类不同,
isinstance(inst, Singleton)
将返回True
。更正
在另一个主题上,您可能已经注意到这一点,但是您原始帖子中的基类实现是错误的。
_instances
需要 在类上引用,你需要使用super()
或者你正在递归,而__new__
实际上 是你必须 传递 给静态方法 的类,而不是类方法,因为实际类在调用时 尚未创建。所有这些事情对于元类实现也是如此。装饰器返回一个类
我本来是写评论的,但是太长了,所以我会在这里添加。 方法 #4 比其他装饰器版本更好,但它的代码比单例所需的要多,而且不清楚它的作用。
主要问题源于该类是它自己的基类。首先,让一个类成为一个几乎相同的类的子类,并且只存在于它的
__class__
属性中,这不是很奇怪吗?这也意味着您不能使用super()
定义 在其基类上调用同名方法的任何方法, 因为它们会递归。这意味着您的类不能自定义__new__
,并且不能从需要调用它们的任何类中派生__init__
。何时使用单例模式
您的用例是想要使用单例 的更好示例 之一。您在其中一条评论中说“对我来说,日志记录似乎一直是单身人士的自然候选人。”你 完全正确。
当人们说单例不好时,最常见的原因是它们是 隐式共享状态。虽然全局变量和顶级模块导入是 显式 共享状态,但传递的其他对象通常是实例化的。这是一个很好的观点, 但有两个例外。
第一个,也是在不同地方提到的一个,是当单例是 常量 时。全局常量的使用,尤其是枚举,被广泛接受,并且被认为是理智的,因为无论如何, 没有一个用户可以为任何其他用户搞砸它们。这同样适用于常量单例。
第二个例外,很少提及,是相反的——当单例 只是一个数据接收器,而不是一个数据源(直接或间接)。这就是为什么记录器感觉像是单例的“自然”用途。由于各种用户 没有以其他用户关心的方式更改记录器,因此 没有真正的共享状态。这否定了反对单例模式的主要论点,并使它们成为合理的选择,因为它们 易于 用于任务。
这是来自 http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html 的引用: