Python单例模式中的问题

科技公元 后端 2025-01-14

Python单例模式中的问题

一、装饰器形式的单例模式

首先先给出Python中装饰器的单例模式:python

代码解读
复制代码
import threading def singleton(cls): _instances = {} _lock = threading.Lock() def get_instance(*args, **kwargs): if cls not in _instances: with _lock: if cls not in _instances: _instances[cls] = cls(*args, **kwargs) return _instances[cls] return get_instance

那么装饰器形式的单例模式会出现什么问题呢?

装饰器单例问题1、无法使用内置函数isinstance()来判断类型

使用isinstance()来判断单例类型的示例:python

代码解读
复制代码
@singleton class MyClass:... a1 = MyClass() a2 = MyClass() assert a1 is a2 assert isinstance(a1, MyClass)

上面的示例执行时会触发异常:sh

代码解读
复制代码
TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union

出错的原因是“isinstance() 的第二个参数必须是一个typetype元组或union。”

示例中传给isinstance()的第二个参数是MyClass,这是一个类,而在Python中,类的类型是type,怎么还报错呢?

打印看一下type(MyClass),输出是function。也就是说,加了@singleton之后,MyClass变成一个函数(function)了,而函数是无法作为isinstance()的第二个参数的。

那么有解决办法吗?一个解决办法就是,使用functions.wrapscls包装起来,然后单例类型使用__wrapped__属性获取原类型。python

代码解读
复制代码
import threading import functools def singleton(cls): _instances = {} _lock = threading.Lock() @functools.wraps(cls) def get_instance(*args, **kwargs): if cls not in _instances: with _lock: if cls not in _instances: _instances[cls] = cls(*args, **kwargs) return _instances[cls] return get_instance @singleton class MyClass:... a1 = MyClass() a2 = MyClass() assert a1 is a2 assert isinstance(a1, MyClass.__wrapped__)

这样执行的时候就不会报错了。

使用functions.wraps没什么问题,使用__wrapped__属性就太不优雅了,还容易出错。IDE可不会提示你应该使用__wrapped__,你自己也无法时刻记住某个类是不是使用了单例模式。

装饰器单例问题2、无法使用"|"符号与其他类型组合成联合类型

使用"|"符号来表示联合类型是 Python3.10 推出的功能。

"|"和单例模式一起使用的示例:python

代码解读
复制代码
@singleton class MyClass:... a1: MyClass | None = None

示例执行时报错:sh

代码解读
复制代码
TypeError: unsupported operand type(s) for |: 'function' and 'NoneType'

报错原因是“'function'和'NoneType'之间不支持 | 操作”,还是'function'的锅。

使用了装饰器单例模式的类,就不能使用|符号来组合类型了,蛋疼。

当然,也不是没有解决之道,可以使用typing模块的功能。python

代码解读
复制代码
a1: Optional[MyClass] = None # 或者 a1: Union[MyClass, None] = None

但是这样的话,会造成风格不统一(有的使用typing.Union来组合类型,有的使用|符号)。或者要风格统一的话(都用typing模块),就不能使用|符号的新功能。

二、元类形式的单例模式

以上两个单例问题之所以存在,是因为装饰器将类包装成了一个函数,而函数的类型是functionfunction无法使用type的一些功能。

那么不使用装饰器,使用其他形式(比如元类)的单例模式,是不是就没有以上的问题呢?确实是。

元类形式的单例模式如下:python

代码解读
复制代码
class SingletonMeta(type): _instances = {} _lock = threading.Lock() def __call__(cls, *args, **kwargs): if cls not in cls._instances: with cls._lock: if cls not in cls._instances: cls._instances[cls] = super().__call__(*args, **kwargs) return cls._instances[cls]

测试isinstance():python

代码解读
复制代码
class MyClass(metaclass=SingletonMeta):... a1 = MyClass() a2 = MyClass() assert a1 is a2 assert isinstance(a1, MyClass)

没问题。再测试|符号:python

代码解读
复制代码
class MyClass(metaclass=SingletonMeta):... a1: MyClass | None = None

也没有问题。

元类形式的单例模式,似乎挺完美的,因为它能解决装饰器单例模式的缺陷。

它真的完美吗?并不。

元类单例问题、可能无法继承或实现同样使用了元类的类或接口

元类形式的单例模式,如果想继承或实现另外一个同样使用了元类的类或接口,就会出现问题。python

代码解读
复制代码
from abc import ABC, abstractmethod class Flyable(ABC): @abstractmethod def fly(self):... class MyClass(Flyable, metaclass=SingletonMeta): def fly(self): print("fly")

以上代码执行的时候报错:sh

代码解读
复制代码
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

出错原因是:“元类冲突:派生类的 元类 必须是其所有基类的 元类 的(非严格)子类。”

abc模块中的ABC类使用了元类ABCMetaMyClass使用了元类SingletonMetaSingletonMeta并不是ABCMeta的子类,所以出现了元类冲突。

有什么解决办法吗?有的,那就是让SingletonMeta成为ABCMeta的子类。

修改后的代码如下:python

代码解读
复制代码
import threading from abc import ABC, abstractmethod, ABCMeta class _SingletonMeta(type): _instances = {} _lock = threading.Lock() def __call__(cls, *args, **kwargs): if cls not in cls._instances: with cls._lock: if cls not in cls._instances: cls._instances[cls] = super().__call__(*args, **kwargs) return cls._instances[cls] # 让`SingletonMeta`成为`ABCMeta`的子类 class SingletonMeta(_SingletonMeta, ABCMeta): ... class Flyable(ABC): @abstractmethod def fly(self):... class MyClass(Flyable, metaclass=SingletonMeta): def fly(self): print("fly") a1 = MyClass() a2 = MyClass() assert a1 is a2

可见元类形式的单例模式,也不是完美的。好在这种打补丁的方法对用户是透明的,不需要修改客户端的代码。

元类形式的单例模式,目前就发现这一个问题。如果有其他问题,等发现了再来补充。

三、模块级单例模式和类属性单例

Python 中,模块本身是单例,可以将单例对象定义在模块级别,这样在导入模块时,就会得到同一个实例。python

代码解读
复制代码
# singleton.py class Singleton:... singleton_instance = Singleton()

单例模式还可以通过类属性来实现,可以在类中定义一个类属性来存储实例,并在 __new__ 方法中控制实例的创建。python

代码解读
复制代码
class Singleton: _instance = None _lock = threading.Lock() def __new__(cls, *args, **kwargs): if not cls._instance: with cls._lock: if not cls._instance: cls._instance = super(Singleton, cls).__new__(cls) return cls._instance # 使用示例 singleton1 = Singleton() singleton2 = Singleton() assert singleton1 is singleton2

问题在于,这两种形式的单例模式,都是无法通用的,即要针对每一种类型都进行单独的实现。

四、总结

Python的单例模式,似乎没有一个完美的实现形式,只能在“矮子里拔将军”。

不能通用的单例模式不必再说。能通用的形式,装饰器单例也不太好,因为会改变原类型,容易影响客户端的代码实现。也就元类形式的单例能看一看了,虽然可能需要打补丁,但至少对用户透明,不会影响客户端的代码。

转载来源:https://juejin.cn/post/7455124976427647014

Apipost 私有化火热进行中

评论