Python中的单子模式

244 阅读4分钟

Singleton Pattern in Python

在这篇文章中,我们已经探讨了Python中的Singleton设计模式的概念。Singleton模式是最常见的设计模式之一。

目标

  • 定义设计模式并列出其类型。
  • 定义Singleton模式
  • 使用Python和类似的OOP语言实现Singleton模式并将其应用于各种应用程序和系统中。
  • 使用Singleton模式编写可重复使用的代码。

什么是设计模式

设计模式是可重复使用的格式化的最佳实践,程序员在设计应用程序或系统时可以用它来解决常见的问题。它们可以被分为创造模式、结构模式、行为模式和并发模式。

单子模式

这是一种处理对象创建的创造型模式。该模式将一个类的实例化限制为一个单一的实例。当只需要一个对象来协调整个应用的行动时,它就被使用。单子模式在日志、驱动对象、缓存和线程池中很有用。Python模块使用了单子模式。Python检查一个模块是否被导入,如果被导入,则返回该模块的对象,如果没有,则创建它。

Singleton Pattern in Python

单身类的UML图示

创建单子类背后的意识形态如下。

  1. 我们将允许在第一次尝试时创建一个单子类实例。
  2. 如果一个实例已经存在,我们将返回先前创建的单子类的实例。

实现这一点可以在 __ 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模式的缺点

  • 全局变量可能会被错误地改变。
  • 对同一个对象可能会创建多个引用
  • 类是紧密耦合的,所以改变一个类会在无意中影响到另一个类