Python:用于有效对象管理的单例模式
1. 写在前面
在本文中,我们将介绍一种常用的软件设计模式 —— 单例模式。
通过示例,演示单例创建,并确保该实例在整个应用程序生命周期中保持一致。同时探讨它在 Python 中的目的、益处和实际应用。
关键点:
1、单例模式只有一个实例存在;
2、单例模式必须自己创建自己的唯一实例;
3、单例模式是一种软件设计模式,而不是专属于某种编程语言的语法;
公众号: 滑翔的纸飞机
现在让我们开始吧!
2. Python 单例模式
那什么是单例模式?
这是个非常简单的问题,基本上就是当我们有一个类时,我们只能实例化该类的一个实例对象,无论你是要优化资源使用、配置数据,还是要为我们的程序优化某个全局只读数据,该模式都提供了一个清晰有效的解决方案。
2.1 实现
因为有不同的方式可以创建。这里我将向你展示几种简单的方法。
2.1.1 使用 __init__
在这里,我将定义一个 Singleton
类并定义一个方法getInstance()
。我们的想法是,无论我在哪里调用,它都会返回 Singleton.getInstance()
的特定实例。
考虑,无论是否已创建类实例,都将返回特定实例对象。因此,在这里使用类变量,跟踪实例是否已创建。
class Singleton:# 类变量 __instance 将跟踪唯一的对象实例__instance = None@staticmethoddef getInstance():if Singleton.__instance == None:Singleton()return Singleton.__instance
现在看起来当然还不是一个单例,接下去通过__init__()
方法(类似于构造函数),在Singleton
对象实例化时被调用。该对象会将类变量设置为对象实例。__init__()
全局只应被调用一次,并且由该类中的方法调用(getInstance()
),保证__instance
类变量赋值之后不允许再此赋值。
def __init__(self):if Singleton.__instance != None:raise Exception("Singleton object already created!")else:Singleton.__instance = self
现在,让我们创建实例验证:
在s1 情况下会调用__init__()
创建实例;
在s2 情况下返回与s1相同实例;
s1 = Singleton.getInstance()
print(s1)
s2 = Singleton.getInstance()
print(s2)
而且如果我们在 s1 上设置属性,s2 也会有相同的值,因为它们都指向同一个对象。
s1.x = 5
print(s2.x)
它将打印以下输出
<__main__.Singleton object at 0x10e24fdf0>
<__main__.Singleton object at 0x10e24fdf0>
5
完整代码示例:
"""
@Time:2023/9/15 01:18
@Author:'jpzhang.ht@gmail.com'
@Describe:
"""class Singleton:# 类变量 __instance 将跟踪唯一的对象实例__instance = Nonedef __init__(self):if Singleton.__instance != None:raise Exception("Singleton object already created!")else:Singleton.__instance = self@staticmethoddef getInstance():if Singleton.__instance == None:Singleton()return Singleton.__instanceif __name__ == '__main__':s1 = Singleton.getInstance()print(s1)s2 = Singleton.getInstance()print(s2)s1.x = 5print(s2.x)
2.1.2 使用 __call__
& metaclass
__call__()
:Python中,只要在创建类时定义了__call__()
方法,这个类就是可调用对象。
针对__call__()
使用参考示例:
class Father:def __call__(self):print('My call() function')fat=Father()
fat()输出:My call() function
很神奇不是,实例对象也可以像函数一样作为可调用对象来用。
接下去通过__call__
实现单例,完整代码示例如下:
"""
@Time:2023/9/15 01:26
@Author:'jpzhang.ht@gmail.com'
@Describe:
"""class Singleton(type):_instances = {}def __call__(cls, *args, **kwargs):if cls not in cls._instances:cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)return cls._instances[cls]class Singleton1(metaclass=Singleton):passif __name__ == '__main__':s1 = Singleton1()print(s1)s2 = Singleton1()print(s2)s1.x = 5print(s2.x)
输出:
<__main__.Singleton1 object at 0x1120c8280>
<__main__.Singleton1 object at 0x1120c8280>
5
2.1.3 使用 __new__
简单介绍下__new__()
:只要是面向对象的编程语言,类的实例化都一定包含两个步骤:
(1)在内存中创建对象,即开辟一块内存空间来存放类的实例(Instance);
(2)初始化对象,即给实例的属性赋予初始值,例如全部填空;
在 python 中,第一步由 __new__
函数负责,第二步由 __init__
函数负责。
在这种方法中,我们将使用__new__()
,让它检查类变量以查看实例是否已创建,如果没有,我们就创建它,无论是否需要创建,我们都会返回实例。
class Singleton:# 类变量 __instance 将跟踪唯一的对象实例__instance = Nonedef __new__(cls):if (cls.__instance is None):cls.__instance = super(Singleton, cls).__new__(cls)return cls.__instance
因此,当第一次创建单例对象时,它会运行__new__
,当判断__instance
为 None,则创建对象并赋值至__instance
类变量。下次运行时,发现__instance
不是 None 已被设置,于是将返回 Singleton.__instance
这里通过调用超类的方法来实际返回对象实例本身,然后再返回它的__new__。
s1 = Singleton()
print(s1)
s2 = Singleton()
print(s2)# 同样,如果在 s1 上设置一个属性,s2将具有相同的值,因为它们都引用同一个对象
s1.x = 5
print(s2.x)
它将打印以下输出
<__main__.Singleton object at 0x10607beb0>
<__main__.Singleton object at 0x10607beb0>
5
完整示例:
"""
@Time:2023/9/16 23:04
@Author:'jpzhang.ht@gmail.com'
@Describe:
"""class Singleton:# 类变量 __instance 将跟踪唯一的对象实例__instance = Nonedef __new__(cls):if (cls.__instance is None):cls.__instance = super(Singleton, cls).__new__(cls)return cls.__instanceif __name__ == '__main__':s1 = Singleton()print(s1)s2 = Singleton()print(s2)# 同样,如果在 s1 上设置一个属性,s2将具有相同的值,因为它们都引用同一个对象s1.x = 5print(s2.x)
2.1.4 装饰器
关于装饰器读者自行查阅其他博文;
本例照旧通过 __instance = {}
来跟踪实例对象。使用类地址作为键,实例作为值,每次创造实例时,检查该类是否存在实例,存在的话直接返回该实例即可,否则新建一个实例并存放在字典中。
函数装饰器示例:
"""
@Time:2023/9/16 23:29
@Author:'jpzhang.ht@gmail.com'
@Describe:
"""def singleton(cls):__instance = {}def inner():if cls not in __instance:__instance[cls] = cls()return __instance[cls]return inner@singleton
class Cls(object):def __init__(self):print('My name Cls')if __name__ == "__main__":cls1 = Cls()print(cls1)cls1.x = 5cls2 = Cls()print(cls2)print(cls2.x)
输出:
My name Cls
<__main__.Cls object at 0x10f284160>
<__main__.Cls object at 0x10f284160>
5
类装饰器示例:
"""
@Time:2023/9/16 23:39
@Author:'jpzhang.ht@gmail.com'
@Describe:
"""class Singleton(object):def __init__(self, cls):self._cls = clsself._instance = {}def __call__(self):if self._cls not in self._instance:self._instance[self._cls] = self._cls()return self._instance[self._cls]@Singleton
class Cls(object):def __init__(self):print('My name Cls')if __name__ == "__main__":# print('=======使用1=======')cls1 = Cls()print(cls1)cls1.x = 5cls2 = Cls()print(cls2)print(cls2.x)# print('=======使用2=======')# cls1 = Singleton(Cls)# cls2 = Singleton(Cls)# print(cls1)# print(cls2)
输出:
My name Cls
<__main__.Cls object at 0x10ede0850>
<__main__.Cls object at 0x10ede0850>
5
3. 使用案例
单例经常与设计模式结合使用,以解决最常见的问题,包括缓存、日志、配置设置和线程池。
案例1:
"""
@Time:2023/9/16 23:09
@Author:'jpzhang.ht@gmail.com'
@Describe:
"""class Logger:_instance = Nonedef __new__(cls):if cls._instance is None:cls._instance = super(Logger, cls).__new__(cls)cls._instance._initialize()return cls._instancedef _initialize(self):self.log_file = open("log.txt", "a")def log(self, message):self.log_file.write(message + "\n")self.log_file.flush()def close(self):self.log_file.close()class LoggerFactory:def create_logger(self):return Logger()# 使用方法
if __name__ == "__main__":logger_factory = LoggerFactory()logger1 = logger_factory.create_logger()logger2 = logger_factory.create_logger()logger1.log("This is a log message from logger1")logger2.log("This is a log message from logger2")logger1.close()logger2.close()
在本例中,创建了一个采用单例模式的日志类Logger
。它确保只创建一个类实例。LoggerFactory类
是一个工厂类,可创建Logger类的实例。
log.txt:This is a log message from logger1
This is a log message from logger2
运行代码后,可以看到两个对象(logger1、logger2),实际是Logger类的同一个实例。这样可以确保日志记录只使用一个日志文件。
案例2:
线程安全是实现单例模式时的一个重要考虑因素,因为多个线程可能会尝试同时访问或创建类的实例。如果没有适当的同步,这可能会导致创建多个实例,从而违反单例原则。
import threading
import randomclass Singleton:_instance = Nonedef __init__(self):self.value = random.randint(1, 10)def __new__(cls):if cls._instance is None:cls._instance = super(Singleton, cls).__new__(cls)return cls._instancedef create_singleton(index):s = Singleton()print(f"Singleton instance created by thread {threading.current_thread().name}: {s} and value: {s.value}\n")# 模拟多个线程同时创建单例
def problem_case():threads = []for i in range(5):thread = threading.Thread(target=create_singleton, args=(i,))threads.append(thread)thread.start()for thread in threads:thread.join()# 使用锁来确保线程安全
class ThreadSafeSingleton:__instance = None__lock = threading.Lock()def __init__(self):self.value = random.randint(1, 10)@classmethoddef get_instance(cls):if not cls.__instance:with cls.__lock:if not cls.__instance:cls.__instance = cls()return cls.__instancedef create_thread_safe_singleton(index):s = ThreadSafeSingleton.get_instance()print(f"Singleton instance created by thread {threading.current_thread().name}: {s} and value: {s.value}\n")def thread_safe_case():threads = []for i in range(5):thread = threading.Thread(target=create_thread_safe_singleton, args=(i,))threads.append(thread)thread.start()for thread in threads:thread.join()if __name__ == "__main__":print("Problem case (without thread safety):")problem_case()print("\nThread-safe case:")thread_safe_case()
在上例中,ThreadSafeSingleton
使用线程锁类创建了一个锁对象,我们可以用它来同步对类变量的访问。然后定义一个类方法,首先检查是否已经创建了该类的实例。如果没有,它会获取锁,并创建该类的新实例。
备注:获取锁后再次校验是否已经创建了该类的实例。
输出:
Problem case (without thread safety):
Singleton instance created by thread Thread-10: <__main__.Singleton object at 0x102881760> and value: 4Singleton instance created by thread Thread-7: <__main__.Singleton object at 0x102881760> and value: 3Singleton instance created by thread Thread-6: <__main__.Singleton object at 0x102881760> and value: 10Singleton instance created by thread Thread-8: <__main__.Singleton object at 0x102881760> and value: 9Singleton instance created by thread Thread-9: <__main__.Singleton object at 0x102881760> and value: 4Thread-safe case:
Singleton instance created by thread Thread-11: <__main__.ThreadSafeSingleton object at 0x102874a60> and value: 2Singleton instance created by thread Thread-12: <__main__.ThreadSafeSingleton object at 0x102874a60> and value: 2Singleton instance created by thread Thread-13: <__main__.ThreadSafeSingleton object at 0x102874a60> and value: 2Singleton instance created by thread Thread-14: <__main__.ThreadSafeSingleton object at 0x102874a60> and value: 2Singleton instance created by thread Thread-15: <__main__.ThreadSafeSingleton object at 0x102874a60> and value: 2
不难看出,上锁后符合单例原则。
3. 最后
实现该模式的每种方法都有自己的优缺点,选择哪种方法可能取决于具体的用例。虽然该模式在某些情况下很有用,但它也可能带来一些缺点,例如使代码更难测试和维护。
总之,单例设计模式是管理应用程序中类的单个实例的强大工具。不过,在使用时应谨慎,考虑系统的具体要求以及对可维护性、可测试性和并发性的潜在影响。
感谢您花时间阅读文章
关注公众号不迷路:)
相关文章:
Python:用于有效对象管理的单例模式
1. 写在前面 在本文中,我们将介绍一种常用的软件设计模式 —— 单例模式。 通过示例,演示单例创建,并确保该实例在整个应用程序生命周期中保持一致。同时探讨它在 Python 中的目的、益处和实际应用。 关键点: 1、单例模式只有…...

【TCP】滑动窗口、流量控制 以及拥塞控制
滑动窗口、流量控制 以及拥塞控制 1. 滑动窗口(效率机制)2. 流量控制(安全机制)3. 拥塞控制(安全机制) 1. 滑动窗口(效率机制) TCP 使用 确认应答 策略,对每一个发送的数…...
Xilinx FPGA管脚约束语法规则(UCF和XDC文件)
文章目录 1. ISE环境(UCF文件)2. Vivado环境(XDC文件) 本文介绍ISE和Vivado管脚约束的语句使用,仅仅是管脚和电平状态指定,不包括时钟约束等其他语法。 ISE使用UCF文件格式,Vivado使用XDC文件&…...

服务网格和CI/CD集成:讨论服务网格在持续集成和持续交付中的应用。
🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…...
代码随想录训练营第56天|583.两个字符串的删除操作,72.编辑距离
代码随想录训练营第56天|583.两个字符串的删除操作,72.编辑距离 583.两个字符串的删除操作文章思路代码 72.编辑距离文章思路代码 总结 583.两个字符串的删除操作 文章 代码随想录|0583.两个字符串的删除操作 思路 如果不按照编辑距离考虑的话,只需要…...
【JDK 8-Lambda】3.1 Java高级核心玩转 JDK8 Lambda 表达式
一、 什么是函数式编程 ? 二、 什么是lambda表达式? 1. 先看两个示例 A.【创建线程】 B.【数组排序-降序】 2. lambda表达式特性 A. 使用场景(前提): B. 语法 (params) -> expression C. 参数列表 D. 方法体 F. 好处 一、 什么是函数式编…...

【C#】XML的基础知识以及读取XML文件
最近在学读取文件 目录 介绍特点结构XML的语法规则XML 命名规则 C#操作XML新建读取第一种第二种第三种 读取属性 介绍 XML (可扩展标记语言,eXtensible Markup Language) 是一种标记语言,它被设计用来传输和存储数据。 特点 可扩展性:由于…...
Immutable.js简介
引子 看一段大家熟悉的代码 const state {str: wwming,obj: {y: 1},arr: [1, 2, 3] } const newState stateconsole.log(newState state) // truenewState和state是相等的 原因: 由于js的对象和数组都是引用类型。所以newState的state实际上是指向于同一块内存…...
C语言进阶教程(位操作和进制数的表示)
文章目录 前言一、左移和右移二、清除对应的位为0和设置对应的位为11.设置对应的位为12.清除对应的位为0 三、进制数的表示四、& ^ | ~总结 前言 本篇文章给大家讲解一下C语言中的位操作,在嵌入式中位操作是经常需要使用的,那么下面就让我们来学习一…...

Loguru:功能强大、简单易用的Python日志库
文章目录 Loguru:Python的日志库安装 Loguru基本用法配置 Loguruadd() 语句remove() 语句设置日志文件保留日志的等级设置控制台日志显示等级Loguru:Python的日志库 Loguru 是一个功能强大、简单易用的日志库,可以让 Python 的日志记录变得更加轻松。它提供了丰富的功能和配…...

idea之maven的安装与配置
我们到maven的官网里下载maven,地址:https://maven.apache.org/download.cgi下载完成后解压即可配置环境变量 此电脑–>右键–>属性–>高级系统设置–>环境变量–>系统变量(S)–>新建一个系统变量 变量名&…...

【最新面试问题记录持续更新,java,kotlin,android,flutter】
最近找工作,复习了下java相关的知识。发现已经对很多概念模糊了。记录一下。部分是往年面试题重新整理,部分是自己面试遇到的问题。持续更新中~ 目录 java相关1. 面向对象设计原则2. 面向对象的特征是什么3. 重载和重写4. 基本数据类型5. 装箱和拆箱6. …...
面试:经典问题解决思路
1. 秒杀系统架构 参考:秒杀系统架构优化思路 2. 如何防止订单重复提交 重复提交原因: 一种是由于用户在短时间内多次点击下单按钮,或浏览器刷新按钮导致。另一种则是由于Nginx或类似于SpringCloud Gateway的网关层,进行超时重试造成的。 方案…...

CG MAGIC分享3ds Max卡顿未保存处理方法有哪些?
3ds Max进行建模、渲染这一系列过程中,大家使用中都会遇到各种原因导致软件卡顿或崩溃是很常见的情况。 可以说卡机没关系,可是卡顿发生时,如果之前的工作没有及时保存,可能会导致数据的丢失和时间的浪费。这就是最让人烦躁的了&…...
[python 刷题] 238 Product of Array Except Self
[python 刷题] 238 Product of Array Except Self 题目: Given an integer array nums, return an array answer such that answer[i] is equal to the product of all the elements of nums except nums[i]. The product of any prefix or suffix of nums is guar…...

UG NX二次开发(C#)-计算直线到各个坐标系轴向的投影角度
文章目录 1、前言2、需求分析3、NXOpen方法实现3.1 创建基准坐标系3.2 然后计算直线到基准坐标系的轴向角度3.3 代码调用4、测试效果为:1、前言 最近有个粉丝问我如何计算直线到坐标系各个轴向的角度,这里用UG NX二次开发(C#)实现。当然,这里的内容是经验之谈,如果有更好的…...
C# ComboBox 和 枚举类型(Enum)相互关联
C# ComboBox 和 枚举类型(Enum)相互关联 目的 在C# Winform面板上的ComboBox选择项,由程序填写某个Enum的各个枚举项目。 在运行中读取ComboBox的选择项,返回Enum数值。 非编程方法 低阶做法可以在winform设计窗口手动填写,但是不会自动跟…...

Linux CentOS7 tree命令
tree就是树,是文件或文件名输出到控制台的一种显示形式。 tree命令作用:以树状图列出目录的内容,包括文件、子目录及子目录中的文件和目录等。 我们使用ll命令显示只能显示一个层级的普通文件和目录的名称。而使用tree则可以树的形式将指定…...

软件设计模式系列之九——桥接模式
1 模式的定义 桥接模式是一种结构型设计模式,它用于将抽象部分与其实现部分分离,以便它们可以独立地变化。这种模式涉及一个接口,它充当一个桥,使得具体类可以在不影响客户端代码的情况下改变。桥接模式将继承关系转化为组合关系…...
构造函数的调用规则
#include <iostream> #include <string> using namespace std; class person{ public:int m_age; // person(){ // cout<<"默认构造的调用"<<endl; // } // person(int age){ // m_ageage; // cout<<"有参构造的调用"<…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...

(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
第7篇:中间件全链路监控与 SQL 性能分析实践
7.1 章节导读 在构建数据库中间件的过程中,可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中,必须做到: 🔍 追踪每一条 SQL 的生命周期(从入口到数据库执行)&#…...