当前位置: 首页 > article >正文

实现单例模式的6种方法(Python)

目录

一. 基于模块的实现(简单,易用)

 二. 重新创建时报错(不好用)

三. 只靠方法获取实例(不好用)

四. 类装饰器

五. 重写__new__方法

六. 元类

七. 总结


单例模式(Singleton Pattern)是一种设计模式,其核心目标是确保一个类只有一个实例存在,并提供一个全局访问点。这种模式在需要控制资源访问,节省系统资源或确保全局一致性的场景中非常有用。下面谈一谈Python中6种实现单例的方案,复杂程度基本上是由易到难的。除了第一种方案,剩下的在多线程环境中都有风险。如果你需要多线程单例,请注意加锁!

一. 基于模块的实现(简单,易用)

模块只有在第一次导入时会被初始化,后续导入直接使用已加载的模块。这让Python模块成为了天然的单例,借助它即可轻松获取一个唯一实例:

class _Singleton:'''一个不开放的单例类'''def __init__(self):self._value = '俺是单例'singleton = _Singleton()

然后,需要用到这个单例的地方直接导入现成的实例:

from module import singletonx = singleton
y = singleton
# x 和 y 是同一个对象吗?
print(x is y)  # True

像math.pi,math.e等的单例效果就是依此实现的。 

 二. 重新创建时报错(不好用)

我们可以自己造一个异常来拒绝多次创建实例,当然不造用现成的也可以。比如:

class SingletonError(Exception):'''不能为单例类创建多个实例'''class 孤狼:_instance = Nonedef __init__(self, age):self.age = age# 第一次创建实例时,_instance 为 None,不报错。# 第二次创建实例时,_instance 不为 None,直接报错。if self.__class__._instance is not None:raise SingletonError('爷是孤狼,一山不容二虎!')self.__class__._instance = self狼大 = 孤狼(5)
狼二 = 孤狼(4)

在这个世界里,不能存在狼二,更别说光头弱了:

 不过,其实新的实例已经被创造出来了。只是在初始化的时候强制程序报错,把这个对象直接“扼杀在摇篮中”了,没能赋值给“狼二”。而且,这种方法就怕人家把异常捕获了,那后面会发生什么就不是我们能预测的了。

三. 只靠方法获取实例(不好用)

在这种方案下,我们必须摒弃传统的实例创建方法,转而利用一个类方法获取实例。

class 孤狼:def __init__(self, age):self.age = age# 必须完全使用这个方法来获取实例@classmethoddef get_instance(cls, age):# 如果没有实例化过,就创建一个实例if not hasattr(cls, '_instance'):cls._instance = cls(age)# 如果已经创建过实例,就返回这个实例return cls._instance狼大 = 孤狼.get_instance(5)
狼二 = 孤狼.get_instance(4)
print(狼二.age) # 5,而不是4
print(狼大 is 狼二) # True

这里并没有真正拒绝像 "孤狼(参数)" 这样的调用方式,要想完全拒绝这种调用,就绕回第二种方案了。因此这种方法又鸡肋又不好用。

你可能会觉得:方案二,三是在搞笑吗?嗯……这种活儿确实不应该用初级编程方法来干,下面我们看剩下的用元编程技巧实现的三种方案。

四. 类装饰器

这种方案是用一个工厂函数取代原来的类,直接看实现方式。不过要说明一下,如果要实现单例的类是不可哈希的,就要把使用的键从类本身改为类名。不过我没有这么干,因为单例一般就是不可变的。

from functools import wrapsdef singleton(cls):_instances = {}@wraps(cls)def wrapper(*args, **kwargs):if cls not in _instances:_instances[cls] = cls(*args, **kwargs)return _instances[cls]return wrapper@singleton
class 孤狼:def __init__(self, age):self.age = age
@singleton
class 圆头耄耋:def __init__(self):self.标志技能 = '哈气'狼大 = 孤狼(5)
狼二 = 孤狼(4)
print(狼二.age) # 5,而不是4
print(狼大 is 狼二) # True猫爹 = 圆头耄耋()
猫爷 = 圆头耄耋()
print(猫爹 is 猫爷) # True

下面解释一下这个类装饰器。

from functools import wrapsdef singleton(cls): #(1)_instances = {} #(2)@wraps(cls) #(3)def wrapper(*args, **kwargs): #(4)if cls not in _instances:_instances[cls] = cls(*args, **kwargs)return _instances[cls]return wrapper #(1)

(1):这个类装饰器以@singleton使用,就相当于编写了 cls = singleton(cls)。会将返回的内层函数赋值给类,让类成为内层函数的引用。

(2):这个_instances字典在内层函数的闭包空间内,内层函数可以直接操作它。

(3):就算是单例,使用@wraps保存元数据也是个好习惯!

(4):内层函数现在“夺舍”了类,接受任意参数。如果类不在_instances中,说明还没有为它创建实例,那就创建一个放到_instances中,最后返回的是_instances中的实例。如果不是首次创建,if条件检查就不会通过,最终返回的是第一次创建的实例。

也可以给类新填一个类属性存储实例,后面元类方案我会展示这两种不同的实现策略。这应该是三种元编程方案中最好的,__new__不够灵活,元类太深奥。

五. 重写__new__方法

__new__方法掌管实例的创建,而不是__init__。更具体地,实例先由__new__创建,然后,如果创建的东西确实是本类的实例,就作为self传给__init__进行一系列属性的赋值,完成初始化。如果不是本类的实例(真的可以这样),就不交给__init__。

不过,实现单例只需要确保每次获取的是同一个实例即可,不用担心“生的孩子不是自己的”。下面看具体实现方法:

class 孤狼:_instance = Nonedef __new__(cls, *args, **kwargs):if not cls._instance:cls._instance = super().__new__(cls)return cls._instancedef __init__(self, age):self.age = age狼大 = 孤狼(5)
狼二 = 孤狼(4)
print(狼大.age)  # 4
print(狼二.age)  # 4
print(狼大 is 狼二)  # True

这种使用_instance的技巧我们已经见过多次了,我就不多解释了。创建实例是委托超类完成的,也就是super().__new__(cls),不需要传入其他参数——这些参数其实就是__init__那里的参数,只不过“生自家孩子”往往用不到罢了。

那为什么这次反而是狼大的age被狼二覆盖了?因为属性age是在__init__中进行赋值的,创建狼二时是最后一次赋值,赋的值是4,所以这个单例的age值从5变成了4。

想要拒绝这种行为,可以在__init__中新增一个条件判断,这时就又是狼大强压狼二了:

class 孤狼:_instance = Nonedef __new__(cls, *args, **kwargs):if not cls._instance:cls._instance = super().__new__(cls)return cls._instancedef __init__(self, age):# 如果没有设置name属性,则设置它if not hasattr(self, 'age'):self.age = age狼大 = 孤狼(5)
狼二 = 孤狼(4)
print(狼大.age)  # 5
print(狼二.age)  # 5
print(狼大 is 狼二)  # True

使用__new__其实很不灵活,对子类的支持不足——基本必须重写子类的__new__方法。相比而言,类装饰器和元类就能轻松支持任何类。

六. 元类

一般地,有其他方案我们就不会动用元类,一切类都是元类的实例,它是Python的“终极武器”和“黑魔法”。一来元类相对而言太高深了,二来元类的接口不一定就比其他方案好使。我就觉得类装饰器超级好用呀!如果要在三种元编程方案中选一个,我肯定会选类装饰器。

元类强大到可以干涉类的创建,初始化,和实例化三个过程。分别依赖元类的__new__,__init__,和__call__方法。现在我们想要插手实例创建的逻辑,应该在__call__上下功夫。

你会发现,下面的元类方案和类装饰器方案很类似,都是在外部存储了一个字典:

class MetaSingleton(type):_instances = {}def __call__(cls, *args, **kwargs):if cls not in MetaSingleton._instances:MetaSingleton._instances[cls] = super().__call__(*args, **kwargs)return MetaSingleton._instances[cls]class 孤狼(metaclass=MetaSingleton):def __init__(self, age):self.age = age狼大 = 孤狼(5)
狼二 = 孤狼(4)
print(狼大.age) # 5
print(狼二.age) # 5
print(狼大 is 狼二) # True

只不过,元类中的实例要委托超类type的__call__来创建,也就是super().__call__(*args, **kwargs)。使用元类的话,在定义类时指定metaclass=……就好了。

也可以把单例存储在类属性中,像下面这样:

class MetaSingleton(type):def __call__(cls, *args, **kwargs):if not hasattr(cls, '_instances'):cls._instances = Noneif cls not in cls._instances:cls._instances = super().__call__(*args, **kwargs)return cls._instances

第一种方式缺点是占用的空间可能更大,而第二种方式缺点是给类新添了一个属性,在做元编程时可能导致意外发生。 

七. 总结

推荐使用模块单例类装饰器,方案二,三就是来搞笑的,剩下的两种方案的话——__new__更复杂且不够灵活好用;动用元类实现单例完全没必要。只有非常少数的情况是非用元类不可的,我们对元类的态度往往是能不用就不用。如果你感兴趣,我这里有一个真正需要元类出马的简单案例:

利用元类优化装饰器接口的方案https://blog.csdn.net/2402_85728830/article/details/148046472

相关文章:

实现单例模式的6种方法(Python)

目录 一. 基于模块的实现(简单,易用) 二. 重新创建时报错(不好用) 三. 只靠方法获取实例(不好用) 四. 类装饰器 五. 重写__new__方法 六. 元类 七. 总结 单例模式(Singleton Pattern)是一种设计模式,其核心目标是确保一个类…...

基于 STM32 的智慧农业温室控制系统设计与实现

摘要 本文提出一种基于 STM32 微控制器的智慧农业温室控制系统设计方案,通过集成多类型环境传感器、执行机构及无线通信模块,实现对温室内温湿度、光照、土壤湿度等参数的实时监测与自动调控。文中详细阐述硬件选型、电路连接及软件实现流程,并附关键代码示例,为智慧农业领…...

深度学习优化器相关问题

问题汇总 各类优化器SGDMomentumNesterovAdagardAdadeltaRMSpropAdam优化器 为什么Adam不一定最优而SGD最优的深度网络中loss除以10和学习率除以10等价吗L1,L2正则化是如何让模型变得稀疏的,正则化的原理L1不可导的时候该怎么办梯度消失和梯度爆炸什么原因&#xff…...

【免费】【无需登录/关注】度分秒转换在线工具

UVE Toolbox 功能概述 这是一个用于地理坐标转换的在线工具,支持两种转换模式: 十进制度 → 度分秒 度分秒 → 十进制度 使用方法 十进制度转度分秒 在"经度"输入框中输入十进制度格式的经度值(例如:121.46694&am…...

常见的垃圾回收算法原理及其模拟实现

1.标记 - 清除(Mark - Sweep)算法: 这是一种基础的垃圾回收算法。首先标记所有可达的对象,然后清除未被标记的对象。 缺点是会产生内存碎片。 原理: 如下图分配一段内存,假设已经存储上数据了 标记所有…...

fpga-编程线性序列机和状态机

一、线性序列机和有限状态机和(状态机-编程思想)的原理 序列机是什么:用计数器对时钟个数计数,根据相应时钟周期下的单个周期时间和计数个数可以确定某个时刻的时间,确定时间后再需要时间点转换电平! 采用…...

力扣面试150题--完全二叉树的节点个数

Day 51 题目描述 思路 根据完全二叉树的规律,完全二叉树的高度可以直接通过不断地访问左子树就可以获取,判断左右子树的高度: 1. 如果相等说明左子树是满二叉树, 然后进一步判断右子树的节点数(最后一层最后出现的节点必然在右子树中) 2. 如…...

Qt 多线程环境下的全局变量管理与密码安全

在现代软件开发中,全局变量的管理和敏感信息的保护是两个重要的课题。特别是在多线程环境中,不正确的全局变量使用可能导致数据竞争和不一致的问题,而密码等敏感信息的明文存储更是会带来严重的安全隐患。本文将介绍如何在 Qt 框架下实现一个…...

内网映射有什么作用,如何实现内网的网络地址映射到公网连接?

在网络环境中,内网映射是一项重要的技术,它允许用户通过外部网络访问位于内部网络中的设备或服务。如自己电脑上的程序提供他人使用,或在家远程管理公司办公OA等涉及不同网络间的通信和数据交互。nat123作为一款老牌的内网映射工具&#xff0…...

BLIP3-o:一系列完全开源的统一多模态模型——架构、训练与数据集

摘要 在近期关于多模态模型的研究中,将图像理解与生成统一起来受到了越来越多的关注。尽管图像理解的设计选择已经得到了广泛研究,但对于具有图像生成功能的统一框架而言,其最优模型架构和训练方案仍有待进一步探索。鉴于自回归和扩散模型在…...

DNS解析流程入门篇

一、DNS 解析流程 1.1 浏览器输入域名 当在浏览器中输入 www.baidu.com 时,操作系统会按照以下步骤进行 DNS 解析: 检查本地 hosts 文件 :操作系统先检查本地的 /etc/hosts 文件,查看是否存在域名与 IP 地址的对应关系。如果找到…...

spring4第2课-ioc控制反转-依赖注入,是为了解决耦合问题

继续学习ioc控制反转&#xff0c; IOC&#xff08;Inversion of Control&#xff09;控制反转&#xff0c;也叫依赖注入&#xff0c; 目的是解决程序的耦合问题&#xff0c;轻量级spring的核心。 1.定义bean.xml <?xml version"1.0" encoding"UTF-8"…...

大模型系列22-MCP

大模型系列22-MCP 玩转 MCP 协议&#xff1a;用 Cline DeepSeek 接入天气服务什么是 MCP&#xff1f;环境准备&#xff1a;VScode Cline DeepSeek**配置 DeepSeek 模型&#xff1a;****配置 MCP 工具****uvx是什么&#xff1f;****安装 uv&#xff08;会自动有 uvx 命令&…...

【监控】Prometheus+Grafana 构建可视化监控

在云原生和微服务架构盛行的今天&#xff0c;监控系统已成为保障业务稳定性的核心基础设施。作为监控领域的标杆工具&#xff0c;Prometheus和Grafana凭借其高效的数据采集、灵活的可视化能力&#xff0c;成为运维和开发团队的“标配”。 一、Prometheus Prometheus诞生于2012…...

vscode里几种程序调试配置

标题调试python嵌入的c代码,例如 import torch from torch.utils.cpp_extension import loadtest_load load(nametest_load, sources[test.cpp],extra_cflags[-O0, -g],#extra_cflags[-O1],verboseTrue, ) a torch.tensor([1, 2, 3]) b torch.tensor([4, 5, 6]) result te…...

RAGFlow源码安装操作过程

RAGFlow是一款基于深度文档理解构建的开源 RAG&#xff08;Retrieval-Augmented Generation&#xff09;引擎&#xff0c;可作为Dify的外部知识库使用[1]。本文主要介绍RAGFlow前端和后端等源码安装操作过程。 一.后端安装 特别注意&#xff1a;python ">3.12,<3…...

Unity使用XCharts动态配置数据——折线图(LineChart)

XCharts官网地址&#xff1a;https://xcharts-team.github.io/ 本地上传资源&#xff1a;https://download.csdn.net/download/m0_64375864/90919669 效果图&#xff1a; 动态配置数据&#xff1a; public class Test3 : MonoBehaviour {public LineChart lineChart;public …...

【HITCSAPP 哈工大计算机系统期末大作业】 程序人生-Hello’s P2P

计算机系统 大作业 题 目 程序人生-Hello’s P2P 专 业 计算机与电子通信类 学   号 2023112915 班   级 23L0505 学 生 杨昕彦 指 导 教 师 刘宏伟 计算机科学…...

DAY9 热力图和箱线图的绘制

浙大疏锦行 学会了绘制两个图&#xff1a; 热力图&#xff1a;表示每个特征之间的影响&#xff0c;颜色越深数值越大表示这两个特征的关系越紧密 箱线图&#xff1a;表示每个特征的数据分布情况 箱体&#xff08;Box&#xff09;&#xff1a; 箱体的上下边界分别表示第一四分位…...

如何查看 GitLab 内置的 PostgreSQL 版本?

GitLab 依赖 PostgreSQL&#xff0c;PostgreSQL 的升级会随着 GitLab 的版本升级而进行&#xff0c;本文分享查看 GitLab 内置 PostgreSQL 版本的方法。 GitLab 版本和 PostgreSQL 版本需要一一对应&#xff0c;默认情况下使用 Omnibus 方式安装的 GitLab 实例会自动升级 Postg…...

VR 技术与病毒分离鉴定:一场奇妙的邂逅​

过去&#xff0c;病毒分离鉴定主要依靠传统实验技术&#xff0c;虽为病毒学发展奠定基础&#xff0c;但在现代病毒研究中有诸多局限。​ 沉浸式操作&#xff0c;告别风险担忧​ VR 技术给病毒分离鉴定带来的最大变革是大幅提升实验安全性。借助 VR 设备&#xff0c;实验者身处高…...

解释一下NGINX的反向代理和正向代理的区别?

大家好&#xff0c;我是锋哥。今天分享关于【解释一下NGINX的反向代理和正向代理的区别?】面试题。希望对大家有帮助&#xff1b; 解释一下NGINX的反向代理和正向代理的区别? NGINX的反向代理和正向代理的区别主要体现在它们的功能和使用场景上。下面我会详细解释它们的定义…...

数学笔记一:标量、向量和矩阵基本概念辨析

一、标量 标量&#xff08;Scalar&#xff09; 是一种仅用数值大小&#xff08;即 “量值”&#xff09;就能完全描述的物理量或数学对象&#xff0c;它不具有方向属性。 例如在实数领域的正数、负数。 在物理学领域的多少斤、多少公斤、水温多少度、气温多少度都是标量。 …...

vue3获取两个日期之间的所有时间

1.获取两个日期之间所有年月日 如图所示&#xff1a; 代码如下&#xff1a; <template><div class"datePicker"><el-date-pickerv-model"value1"type"daterange"range-separator"至"start-placeholder"开始时间…...

Python 实现简易版的文件管理(结合网络编程)

目录 一、Python 代码实现1. 服务器端2. 客户端 二、结果展示1. 查看当前路径下的内容 ls2. 切换当前路径 cd3. 查看当前路径 pwd4. 显示根目录下的树状结构 tree5. 在当前路径下创建目录 mkdir6. 删除当前路径下的文件或目录 rm7. 复制文件 mv8. 移动文件 cp9. 用户从当前路径…...

元组可以比较大小吗?一次返回多个值?编程语言的元组?声明变量一定需要指定类型吗?

目录 元组可以比较大小吗? 一次返回多个值? 编程语言的元组 支持元组的语言 元组的基本特性 元组的初始化和使用 声明变量一定需要指定类型吗? var类型 元组可以比较大小吗? 不同编程语言对元组的定位稍有差异&#xff0c;是否可以比较大小随语言而定。 Swift支持…...

PXC集群

PXC集群 一、环境介绍二、PXC安装1、关闭默认mysql模块2、安装yum源3、准备pxc安装环境4、安装pxc5、启动mysql&#xff0c;并更改root密码 三、搭建PXC集群1、编辑/etc/my.cnf 配置文件&#xff08;1&#xff09;pxc1节点配置文件&#xff08;2&#xff09;pxc2节点配置文件&a…...

线程安全问题的成因

前言 大家晚上好呀~~ 今天学习了线程不安全问题的成因。线程安全问题是十分重要的知识点&#xff0c;我想把我所学的与大家分享一波&#xff0c;希望可以帮助到有需要的人&#xff0c;同时加深自己对于线程安全问题的理解。 分析过程如下 结语 今天心情还不错~ 要坚持持续…...

零基础远程连接课题组Linux服务器,安装anaconda,配置python环境(换源),在服务器上运行python代码【3/3 适合小白,步骤详细!!!】

远程连接服务器 请查阅之前的博客——零基础远程连接课题组Linux服务器&#xff0c;安装anaconda&#xff0c;配置python环境&#xff08;换源&#xff09;&#xff0c;在服务器上运行python代码【1/3 适合小白&#xff0c;步骤详细&#xff01;&#xff01;&#xff01;】&am…...

字节跳动BAGEL-7B-MoT模型开源:多模态AI技术的新范式与行业涟漪

在人工智能领域&#xff0c;技术开源与商业化落地的平衡始终是核心议题。2025年5月26日&#xff0c;字节跳动发布开源多模态AI模型BAGEL-7B-MoT&#xff0c;凭借其混合架构设计与跨模态处理能力&#xff0c;在图像生成、视觉理解等任务中展现出与GPT-4o等闭源模型抗衡的实力。这…...