这个问题不是为了讨论单例设计模式是否可取,是否是一种反模式,或者任何宗教战争,而是讨论如何以最 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
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__
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()
__init__
一个模块文件singleton.py
singleton.py
我会推荐 Method #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__。
if
Singleton.__call__
关于元类的几句话。元类是 类的类 ;也就是说,一个类是 它的元类的一个实例 。你可以在 Python 中找到一个对象的元类type(obj)。普通的新式类是 type type。Logger在上面的代码中将是 type class 'your_module.Singleton',就像(唯一)实例Logger将是 type class 'your_module.Logger'。当你用 调用 logger 时Logger(),Python 首先询问 , 的元类要做什么Logger,Singleton从而允许实例创建被抢占。这个过程与 Python 询问一个类__getattr__当你通过调用它的一个属性时调用它来做什么是一样的myclass.attribute。
type(obj)
type
Logger
class 'your_module.Singleton'
class 'your_module.Logger'
Logger()
Singleton
__getattr__
myclass.attribute
元类本质上决定 了类的定义意味着什么 以及如何实现该定义。参见例如http://code.activestate.com/recipes/498149/struct ,它本质上使用元类在 Python 中重新创建 C 样式。线程元类有哪些(具体)用例?还提供了一些示例,它们通常似乎与声明式编程有关,尤其是在 ORM 中使用时。
struct
在这种情况下,如果您使用 Method #2* ,并且子类定义了一个方法,那么 每次 调用时__new__都会执行该方法——因为它负责调用返回存储实例的方法。使用元类,它只会在创建唯一实例时 被调用一次。 您想 自定义调用 class 的含义 ,这由它的类型决定。 SubClassOfSingleton() *
SubClassOfSingleton()
一般来说,使用元类来实现单例 是有意义的。 单例是特殊的,因为 它只创建一次 ,而元类是您自定义 创建类 的方式。如果您需要以其他方式自定义单例类定义,则使用元类可为您提供 更多控制。
您的单例 不需要多重继承 (因为元类不是基类),但是对于使用多重继承 的已创建类 的子类,您需要确保单例类是第 一个/最左边 的具有重新定义的元类__call__这不太可能成为问题。实例字典 不在实例的命名空间中 ,因此它不会意外覆盖它。
__call__
您还会听说单例模式违反了“单一职责原则”——每个类应该 只做一件事 。这样一来,如果您需要更改另一件事,您就不必担心会弄乱代码所做的一件事,因为它们是独立的且被封装的。元类实现 通过了这个测试 。元类负责 执行模式 ,创建的类和子类不需要 知道它们是单例 。 方法 #1 未能通过此测试,正如您在“MyClass 本身是一个函数,而不是类,因此您不能从中调用类方法”中所指出的那样。
编写适用于 Python2 和 3 的东西需要使用稍微复杂的方案。由于元类通常是 typetype的子类,因此可以使用一个在运行时动态创建一个中间基类,并将其作为元类,然后将 其 用作公共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.
isinstance(inst, Singleton)
True
在另一个主题上,您可能已经注意到这一点,但是您原始帖子中的基类实现是错误的。_instances需要 在类上引用 ,您需要使用super()或者您正在 递归 ,并且__new__实际上是您必须将 类传递给 的静态方法,而不是类方法,因为实际类 尚未创建 时它叫做。所有这些事情对于元类实现也是如此。
_instances
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__调用它们的任何类派生。
__class__
您的用例是想要使用单例 的更好示例之一。 您在其中一条评论中说“对我来说,日志记录似乎一直是单身人士的自然候选人。” 你 完全正确 。
当人们说单例不好时,最常见的原因是它们是 隐式共享状态 。虽然全局变量和顶级模块导入是 显式 共享状态,但传递的其他对象通常是实例化的。这是一个很好的观点, 但有两个例外 。
第一个,也是在不同地方提到的一个,是当单例是 常量 时。全局常量的使用,尤其是枚举,被广泛接受,并且被认为是理智的,因为无论如何, 没有一个用户可以为任何其他用户搞砸它们 。这同样适用于常量单例。
第二个例外,很少提及,是相反的——当单例 只是一个数据接收器 ,而不是一个数据源(直接或间接)。这就是为什么记录器感觉像是单例的“自然”用途。由于各种用户没有以其他用户关心的方式 更改记录器,因此 没有真正的共享状态 。这否定了反对单例模式的主要论点,并使它们成为合理的选择,因为它们 易于 用于任务。
这是来自http://googletesting.blogspot.com/2008/08/root-cause-of- singletons.html的引用:
现在,有一种单例是可以的。这是一个单例,其中所有可访问的对象都是不可变的。如果所有对象都是不可变的,那么 Singleton 就没有全局状态,因为一切都是不变的。但是很容易把这种单例变成可变单例,很滑坡。因此,我也反对这些 Singletons,不是因为它们不好,而是因为它们很容易变坏。(作为旁注,Java 枚举就是这类单例。只要您不将状态放入枚举中就可以了,所以请不要。) 另一种半可接受的单例是那些不影响代码执行的单例,它们没有“副作用”。日志记录就是一个很好的例子。它加载了单例和全局状态。这是可以接受的(因为它不会伤害您),因为无论是否启用给定的记录器,您的应用程序的行为都没有任何不同。这里的信息流向一种方式:从您的应用程序到记录器。即使认为记录器是全局状态,因为没有信息从记录器流入您的应用程序,记录器也是可以接受的。如果您希望您的测试断言正在记录某些内容,您仍然应该注入您的记录器,但一般来说,尽管记录器充满了状态,但它并没有害处。
现在,有一种单例是可以的。这是一个单例,其中所有可访问的对象都是不可变的。如果所有对象都是不可变的,那么 Singleton 就没有全局状态,因为一切都是不变的。但是很容易把这种单例变成可变单例,很滑坡。因此,我也反对这些 Singletons,不是因为它们不好,而是因为它们很容易变坏。(作为旁注,Java 枚举就是这类单例。只要您不将状态放入枚举中就可以了,所以请不要。)
另一种半可接受的单例是那些不影响代码执行的单例,它们没有“副作用”。日志记录就是一个很好的例子。它加载了单例和全局状态。这是可以接受的(因为它不会伤害您),因为无论是否启用给定的记录器,您的应用程序的行为都没有任何不同。这里的信息流向一种方式:从您的应用程序到记录器。即使认为记录器是全局状态,因为没有信息从记录器流入您的应用程序,记录器也是可以接受的。如果您希望您的测试断言正在记录某些内容,您仍然应该注入您的记录器,但一般来说,尽管记录器充满了状态,但它并没有害处。