何时在Python中使用单例模式?

9

有许多与在Python中使用Singleton模式相关的问题,尽管这个问题可能重复讨论过的许多方面,但我没有找到以下具体问题的答案。

假设我有一个类,我想只实例化一次。在Python中,我可以在代码中按如下方式实现:

class MyClass(object): 
    def foo(self):
        ....


instance = MyClass()

然后在任何其他程序中,我可以简单地使用以下方式引用该实例:

import myclass
myclass.instance.foo()

在什么情况下这种方法足够使用?在什么情况下使用单例模式是有用/强制的?

4
考虑在一个大型应用程序中的日志记录机制,为此您希望有一个单一的日志记录器对象可以随处访问,而无需到处传递它,您可以只实例化一个新对象,这里可以使用单例模式。 - avasal
考虑到Python可以同时执行面向对象和简单命令式操作,您(或任何人)的使用情况可能只需要一个具有函数“foo”的模块即可满足。 - akaIDIOT
在编程中,最好的例子之一(在我看来)是连接池。例如,当连接到数据库时,您希望限制总连接数并尽可能重用旧连接。这是单例/博格模式方法的完美应用场景。 - Wolph
1
记录日志是一个不好的例子:为每个模块创建单独的记录器,这样你就可以独立地控制它们。连接池听起来很好,直到你为其编写单元测试时,你可能想为每个测试创建一个新的池。通常情况下,单元测试和单例模式并不适合,我更喜欢可测试的代码。 - Duncan
3个回答

9
单例模式更多是出于方便而非必要的考虑。与其他语言不同,Python在测试中很容易模拟单例(只需覆盖全局变量!)。但是,创建单例时仍然要问自己一个问题:我这样做是为了方便还是因为只有一个实例是必需的?未来是否可能会有多个实例?
如果您创建的类确实只会被构建一次,则将其状态作为模块的一部分并将其方法转换为模块级函数可能更有意义。如果假设只有一个实例的假设将来可能发生改变,则通常最好通过传递单例实例而不是通过全局名称引用单例。
例如,您可以使用以下方式轻松实现“单例”:
if __name__ == '__main__':
   instance = MyClass()
   doSomethingWith(instance)

在上述代码中,“instance”是单例,因为它只被构造一次,但处理它的代码提供了实例而不是引用“module.instance”,这使得在某些未来情况下需要多个“MyClass”时更容易重用代码的部分。

这种方法只适用于模块在主代码中使用的情况。在我的情况下,这个类(只能存在一个实例)仅在其他模块中使用。 - Alex
模块级实例的问题在于,如果有一天你想转换为懒惰构造对象(而不是导入时构造),那么这将是相当棘手的。类属性更适合此目的 - 您可以将其转换为“property”。 - kerim
1
@kerim,然后您可以重构模块以在首次访问时实例化其状态。 - Deestan
@Deestan - 你在Python中怎么做? - kerim
@martineau - 我认为模块状态/功能的访问纯粹是通过函数,因为这是唯一对我有意义的实现。否则,它将不是单例替代品,而只是一堆全局状态。 - Deestan
显示剩余3条评论

3
假设您想像Michael Aaron Safyan建议的那样将模块用作单例,即使该模块未被主代码导入,您也可以通过执行以下操作之一使其工作(在主代码或直接或间接导入的模块中)。它所做的是创建一个名为instance的类属性并将其初始化为1,然后使用创建的实例替换sys.modules中的模块对象。
class _MyClass(object):
    def foo(self):
        print 'foo()'

_MyClass.instance = _MyClass()

import sys
_ref = sys.modules[__name__]  # Reference to current module so it's not deleted
sys.modules[__name__] = _MyClass.instance

我发现单例模式是一种有用的实现“注册表”(registry)的方式,当只需要一个(注册表)时,比如类工厂的一组类、常量组或配置信息束。在许多情况下,普通的Python模块就可以了,因为它们已经被缓存在sys.modules字典中,所以默认情况下它们就是单例。
然而,有时候类实例更可取,因为它们可以传递构造参数并具有属性——内置模块对象没有并且不能拥有。像这样的限制可以通过上面展示的技巧来解决,这有效地将自定义类实例转换为模块对象。
使用类实例作为模块对象的思想来自于Alex MartelliActiveState配方,名为Python中的常量

1
在我看来,单例模式有两个方面的作用。
  • 对于某些服务来说,你希望只有一个上下文,因为多个上下文没有意义。
  • 你希望绝对防止人们创建给定类型的两个对象,因为这可能会破坏你的服务。
尽管第一种情况可能有一些应用(例如日志服务),但第二种情况通常是设计不良的标志。
你应该设计你的API,使得你的用户不必考虑这个问题。但是,如果他们深入挖掘你未记录的层以找到你隐藏的构造函数,并且出于任何原因想要使用它,他们不应该被迫处理无用的结构,这些结构被创建用于防止他们做他们需要做的事情。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接