首页 > 编程语言 >Python单例模式中那些蛋疼的问题

Python单例模式中那些蛋疼的问题

时间:2025-01-02 14:59:56浏览次数:1  
标签:__ Python 元类 instances 单例 MyClass 蛋疼 cls

本文中讨论的单例模式都是线程安全的。

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

首先先给出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()来判断单例类型的示例:

@singleton  
class MyClass:...  
  
a1 = MyClass()  
a2 = MyClass()  
assert a1 is a2  
assert isinstance(a1, MyClass)

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

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__属性获取原类型。

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 推出的功能。

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

@singleton  
class MyClass:...  
  
a1: MyClass | None = None

示例执行时报错:

TypeError: unsupported operand type(s) for |: 'function' and 'NoneType'

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

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

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

a1: Optional[MyClass] = None
# 或者
a1: Union[MyClass, None] = None

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

二、元类形式的单例模式

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

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

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

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()

class MyClass(metaclass=SingletonMeta):...  
  
a1 = MyClass()  
a2 = MyClass()  
assert a1 is a2  
assert isinstance(a1, MyClass)

没问题。
再测试|符号:

class MyClass(metaclass=SingletonMeta):...  

a1: MyClass | None = None

也没有问题。

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

它真的完美吗?并不。

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

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

from abc import ABC, abstractmethod  

class Flyable(ABC):  
    @abstractmethod  
    def fly(self):...  
  
class MyClass(Flyable, metaclass=SingletonMeta):  
    def fly(self):  
        print("fly")

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

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的子类。

修改后的代码如下:

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 中,模块本身是单例,可以将单例对象定义在模块级别,这样在导入模块时,就会得到同一个实例。

# singleton.py
class Singleton:...

singleton_instance = Singleton()

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


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的单例模式,似乎没有一个完美的实现形式,只能在“矮子里拔将军”。

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

五、环境说明

  • 操作系统: macOS 15.2
  • 编程语言: Python 3.10
  • 开发工具:
    • PyCharm 2024.3.1
    • Jupyter Notebook 7.3.2

标签:__,Python,元类,instances,单例,MyClass,蛋疼,cls
From: https://www.cnblogs.com/linsuiyuan/p/18647703

相关文章

  • python基础while循环(break、continue)、格式化输出、运算符
    day2while循环break、continue相关知识、格式化输出打印1~100的数字a=1whilea<=100:print(a)a=a+1#continue结束本次循环,开始下一次开启下一次循环break直接结束循环flag=Truewhileflag:print(1)print(2)flag=Falsecontinueprint......
  • Python多线程与类方法的交互:锁提升安全性的奥秘
    目录一、Python多线程与类方法的交互案例1:多线程调用类方法二、为什么需要锁?案例2:使用锁来确保线程安全三、锁的工作原理四、锁的优缺点五、总结在Python编程中,多线程是一种提高程序运行效率的有效手段。特别是在处理I/O密集型任务时,多线程能够显著减少程序的等待时......
  • 深入理解 Python 的 eval() 函数与空全局字典 {}
    目录一、eval()函数基础二、全局字典{}的作用案例1:无全局字典案例2:空全局字典三、为什么使用空全局字典{}可能不安全?案例3:绕过空全局字典的限制四、更安全地使用eval()五、替代方案六、总结在Python编程中,eval()函数是一个强大但常被误解的工具。它能够将......
  • Python多线程使用
    在Python中,多线程是一种利用线程并发执行任务的技术,特别适合用于I/O密集型任务(如文件操作、网络请求等)。Python的多线程可以通过`threading`模块实现。以下是关于Python多线程的一些关键点和示例代码:---###**1.基本概念**-**线程**是一个轻量级的执行单元,与进程不同,多个......
  • Python实现Zip文件的暴力破解
    Python实现Zip文件的暴力破解实验内容我们在网上好不容易下载到一个想要的zip资源却发现这个zip文件是加密的,或者忘掉自己压缩后的密码(一想到就头疼)。这时候我们就会想办法,将里面的内容提取出来。我目前已知的破解zip的方式只有“Knownplaintextattack(已知明文攻击)”......
  • Python OpenCV 图像处理中的应用实例
    1.图像读取与显示这是图像处理的第一步,也是最简单的一步。使用OpenCV读取图像并显示它。importcv2#读取图像image=cv2.imread('example.jpg')#显示图像cv2.imshow('Image',image)cv2.waitKey(0)#等待按键cv2.destroyAllWindows()#关闭所有窗口2.图像......
  • WxPython跨平台开发框架之动态菜单的管理和功能权限的控制
    在一个业务管理系统中,如果我们需要实现权限控制功能,我们需要定义好对应的权限功能点,然后在前端界面中对界面元素的可用性和功能点进行绑定,这样就可以在后台动态分配权限进行动态控制了,一般来说,权限功能点是针对角色进行控制的,也就是简称RBAC(RoleBasedAccessControl)。对于登录系......
  • 项目44:简易拼写检查器【源代码】 --- 《跟着小王学Python·新手》
    项目44:简易拼写检查器—《跟着小王学Python·新手》《跟着小王学Python》是一套精心设计的Python学习教程,适合各个层次的学习者。本教程从基础语法入手,逐步深入到高级应用,以实例驱动的方式,帮助学习者逐步掌握Python的核心概念。通过开发游戏、构建Web应用、编写网络爬......
  • Python知识点精汇:异常信息及如何捕获
    目录一、什么是异常二、异常信息(1)如何去找(2)异常信息有哪些三、捕获异常(1)捕获所有异常(2)捕获特定异常(3)捕获多个异常(4)捕获特定异常后改为别名(5)对多个异常作出多个处理(6)其他查找异常信息,直接翻看(2)异常信息有哪些一、什么是异常    简单来说,程序在运行时,如......
  • 项目45:简易同义词替换器【源代码】 --- 《跟着小王学Python·新手》
    项目45:简易同义词替换器—《跟着小王学Python·新手》《跟着小王学Python》是一套精心设计的Python学习教程,适合各个层次的学习者。本教程从基础语法入手,逐步深入到高级应用,以实例驱动的方式,帮助学习者逐步掌握Python的核心概念。通过开发游戏、构建Web应用、编写网络......