
在这篇文章中,我们已经探讨了Python中的Singleton设计模式的概念。Singleton模式是最常见的设计模式之一。
目标
- 定义设计模式并列出其类型。
- 定义Singleton模式
- 使用Python和类似的OOP语言实现Singleton模式并将其应用于各种应用程序和系统中。
- 使用Singleton模式编写可重复使用的代码。
什么是设计模式
设计模式是可重复使用的格式化的最佳实践,程序员在设计应用程序或系统时可以用它来解决常见的问题。它们可以被分为创造模式、结构模式、行为模式和并发模式。
单子模式
这是一种处理对象创建的创造型模式。该模式将一个类的实例化限制为一个单一的实例。当只需要一个对象来协调整个应用的行动时,它就被使用。单子模式在日志、驱动对象、缓存和线程池中很有用。Python模块使用了单子模式。Python检查一个模块是否被导入,如果被导入,则返回该模块的对象,如果没有,则创建它。

单身类的UML图示
创建单子类背后的意识形态如下。
- 我们将允许在第一次尝试时创建一个单子类实例。
- 如果一个实例已经存在,我们将返回先前创建的单子类的实例。
实现这一点可以在 __ new __ python魔法方法中完成。代码如下。
class Singleton:
def __new__(cls, *args):
if not hasattr(cls, 'instance'):
cls.instance = super(Singleton, cls).__new__(cls)
return cls.instance
s = Singleton() # newly created instance
s1 = Singleton() # returned the previously created instance - s
print(f"{s=}\n{s1}")
在前面的片段中, __ new __ magic方法被重载以控制对象的创建。s 对象被 __ new __ 方法创建,而s1 返回先前创建的对象实例。这个片段背后的逻辑是:hasattr 内置函数检查要创建的对象类是否有实例属性,如果没有,将创建一个新的对象,如果它有实例属性,将返回先前创建的对象。你还会注意到,两个对象的id 是一样的。结果如下所示。
s=<__main__.Singleton object at 0x7fe9df1986d0>
s1=<__main__.Singleton object at 0x7fe9df1986d0>
上面的实现是Singleton模式的Gang of Four(GoF)实现。还有另外一种方法,我们可以声明一个Singleton类。这背后的想法是,对象可以随心所欲地被创建,但它们都应该共享相同的状态和行为。这就是我们所说的Borg 或 Monostate 模式。
Python 使用 __ dict __ 来存储一个类的每个对象的状态,这就是我们要操作的,以便在一个类的所有对象中共享状态。下面是具体的实现。
class Borg:
__shared_state = {'borg': 'Monostate'}
def __init__(self):
self.x = 'Design Pattern'
self.__dict__ = self.__shared_state
pass
b = Borg()
b1 = Borg()
b.x = 2100
print(f"b {b.__dict__}, {b.x=}")
print(f"b1 {b1.__dict__}, {b1.x=}")
前面的片段的结果在下面给出。
b {'borg': 'Monostate', 'x': 2100}, b.x=2100
b1 {'borg': 'Monostate', 'x': 2100}, b1.x=2100
单子和元类
元类是一个类的类。正是通过元类,程序员能够从python预定义的类中创建他们的类。事实上,Python中的所有东西都是一个对象。
由于元类对类的创建和对象的实例化有更多的控制,它可以被用来创建Singletons。python中的元类覆盖了 __ new __ 和 __ init __ 或 __ call __ 魔法方法。下面是用元类实现Singleton的示例代码。
class MetaSingleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(MetaSingleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Logger(metaclass=MetaSingleton):
pass
log1 = Logger()
log2 = Logger()
print(f"{log1=}\n{log2=}")
上述片段的结果显示如下。
log1=<__main__.Logger object at 0x7f5e89a79450>
log2=<__main__.Logger object at 0x7f5e89a79450>
除此以外,记录器实例是同一个对象(相同的id,即指向同一个对象)。
Singleton模式的现实世界的例子
我们现在将考虑一个云服务的例子,它涉及到对数据库的多次读写操作。我们在这里需要单子的是数据库,因为网络应用将调用一个最终在数据库上操作的API。我们将使用sqlite3,它与python捆绑在一起。代码如下所示。
import sqlite3
class MetaSingleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(MetaSingleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Database(metaclass=MetaSingleton):
connection = None
def connect(self):
if self.connection is None:
self.connection = sqlite3.connect('db.sqlite3')
self.cursorobj = self.connection.cursor()
return self.cursorobj
db1 = Database().connect()
db2 = Database().connect()
print(f"{db1=}\n{db2=}")
结果如下:
db1=<sqlite3.Cursor object at 0x7f5e88502340>
db2=<sqlite3.Cursor object at 0x7f5e88502340>
从上面,你可以看到cursorobj ,所有从Database 类创建的对象都是一样的,包括Database 类(Gof Singleton)。
Singleton模式的缺点
- 全局变量可能会被错误地改变。
- 对同一个对象可能会创建多个引用
- 类是紧密耦合的,所以改变一个类会在无意中影响到另一个类