Python设计模式之单例模式实例
前言单例模式Singleton Pattern出自 1994 年的《设计模式》一书作者常被合称为「四人组」GoF。它的意图用一句话概括就是保证一个类只有一个实例并提供一个访问它的全局入口。听起来简单可它大概是所有设计模式里争议最大的一个——在很多现代工程实践里它被直接归入「反模式」名单。这里要先澄清一个常见误解单例不是「只实例化一次」这么简单。它同时承诺了三件事——唯一性、全局可访问、延迟或受控的创建。很多人写的所谓单例只做到了「有个全局变量」既没有受控创建也没有考虑并发和继承那其实不是单例模式只是把全局状态换了个壳。本文从设计模式的视角讲三件事GoF 定义的结构是什么、它本来是想解决什么问题、以及为什么它今天会被这么多人批评。至于「具体怎么用 Python 写出来」和「几种写法各有什么坑」属于更偏实现的话题本文只在必要处点到为止。一、意图与结构GoF 对单例的意图描述是确保一个类只有一个实例并提供一个全局访问点。它的结构里只有一个角色——单例类自己负责私有化创建入口防止外部随意 new。在 Python 里没有真正意义上的「私有构造」常见做法是改写__new__或用一个专门的工厂函数来拦截。内部持有一个类级别的实例引用。对外暴露一个静态/类方法返回那个实例第一次被调用时才创建。这个结构解决的问题是某些资源在一个进程里只应该有一份而且需要被很多地方访问。典型代表是配置、日志器、连接池。二、典型适用场景场景为什么「唯一」有意义配置对象全程序应读同一份配置不能各读各的日志器多个 logger 争抢同一个文件句柄会乱序、丢日志连接池池是多份反而会耗尽后端连接数硬件/设备句柄一个物理设备通常只允许一个控制通道进程内缓存多份缓存等于没有缓存命中率被稀释注意这些场景的共同点它们管的是「稀缺资源」或「全局一致的状态」。如果某个东西既不是稀缺资源也不需要全局一致那用单例就没有正当理由。对比一下如果你只是想要一个「带默认值的字典」那用模块级常量就够了不涉及任何创建控制如果你想要的是「全局变量的替代品」那单例只是给全局变量套了层类的外壳问题一点没解决。三、它长什么样唯一性与全局访问点单例承诺的两个特征——唯一和全局可访问——用一个模块级的对象就能表达清楚。下面这段代码不是「单例模式的实现」只是用来说明它想达到的效果程序里只有一份日志出口所有调用都往同一处写。# 适用于 Python 3.8_log_lines []def emit(level, msg):_log_lines.append(f[{level}] {msg})def dump():return list(_log_lines)emit(INFO, 启动完成)emit(WARN, 配置项缺失)print(dump()) # [[INFO] 启动完成, [WARN] 配置项缺失]print(len(_log_lines)) # 2两次调用写进了同一份列表推演一遍emit每次都在往同一个模块级列表里追加所以dump()能看到这两条记录。「唯一」体现在所有调用共享同一个列表「全局访问点」体现在任何模块只要导入emit就能写进同一处。这也顺带暴露了单例的本质所谓单例实质是一段被共享的可变状态——而共享可变状态正是它最大的风险来源。四、为什么它常被称为反模式批评集中在以下几点。第一它把全局可变状态合法化了。单例状态可以被任意位置的代码读写任何一处修改都可能影响别处。这类 bug 的排查难度极高因为「谁改的」在代码里看不出来。单例只是把「全局变量」这个老问题换了个更好听的名字。第二它隐藏依赖关系。一个函数内部直接Config().data[timeout]从签名上完全看不出它依赖配置。调用方无法从类型或参数上知道「这个方法需要什么才能工作」。相比之下把配置作为参数传进去依赖是一目了然的。第三它让测试难以隔离。单元测试需要「每个用例从干净状态开始」但单例的状态会跨用例保留。第一个用例改了配置第二个用例就拿到被污染的值。要隔离就得在setUp里手动重置单例内部状态——而这段重置代码本身又跟实现细节耦合。第四它经常被用来「省内存」。有人用一个单例缓存大量数据理由是「多个实例太浪费」。但真正的问题通常是设计没分层而不是实例太多。第五它假设了「进程内唯一」等于「全局唯一」。一旦用了多进程每个进程各有自己的一份模块状态彼此不再一致。这一点在使用多进程做 CPU 密集任务时尤其容易踩。# 适用于 Python 3.8import multiprocessing as mp_state []def child_append(tag):_state.append(tag) # 改的是子进程里那一份 _statereturn len(_state)if __name__ __main__:_state.append(main)with mp.Pool(1) as pool:pool.apply(child_append, (child,))print(_state) # [main]子进程的追加没有反映到父进程上面这段的重点不是并发技巧而是要说明单例的边界是进程。跨进程共享状态必须靠外部的共享机制数据库、缓存服务、队列单例给不了你。五、什么情况下仍然值得用单例并非一无是处。在下面这些条件下用它是合理的资源确实稀缺且多份会引发实质问题连接池、设备句柄。创建成本高而全局只需要一份。这个对象基本无状态或者状态是只读的比如一份加载后就不再变的配置。反过来如果这个对象是有状态的、会被频繁修改的或者你只是因为「传参太麻烦」而想图省事那应该优先考虑依赖注入把对象创建好当成参数传给需要它的地方。这样依赖显式、测试可替换、作用域可控。常见坑点把单例当成高级全局变量。❌ 用一个单例装各种互不相关的开关和计数器谁都能改。✅ 全局可变状态能免则免确实需要就限定职责并明确谁写谁读。单例里塞进过多职责。❌ 一个 AppContext 单例同时管配置、日志、数据库连接和缓存最后变成谁都不敢动的巨类。✅ 拆成多个职责单一的组件各自用依赖注入选定生命周期。依赖在函数内部隐式获取。❌ 函数体里直接Config()签名上看不出任何依赖。✅ 把依赖作为参数传进来或者至少存成实例属性让依赖显式可见。测试之间共享单例状态。❌ 用例 A 改了单例的配置用例 B 拿到被污染的值测试结果随机失败。✅ 用依赖注入在每个用例里传独立实例退而求其次也要在setUp里显式重置。以为单例能跨进程统一。❌ 用单例当「全局注册表」多进程任务里各进程各改各的最后结果对不上。✅ 明确单例只作用于单个进程跨进程共享要用外部服务。在模块导入时立刻创建实例。❌ 模块顶层直接instance Heavy()导入即触发耗时的连接或读盘还可能引发循环导入。✅ 延迟到第一次真正使用时再创建或干脆用依赖注入由调用方决定时机。为了「省内存」而给数据缓存套单例。❌ 把大对象塞进单例缓存以为省了内存其实只是让生命周期变得不可控。✅ 先确认内存真的有问题需要缓存就用带淘汰策略的显式缓存对象。认为单例必须由类来实现。❌ 写一大堆__new__逻辑只为得到一个「全局唯一的东西」。✅ Python 里一个模块本身就是天然的命名空间模块级对象天生唯一先考虑这个最简单的方式。总结维度单例的表现解决的问题稀缺资源或全局一致状态只需要一份结构单例类自己控制创建并对外提供访问点代价全局可变状态、依赖隐藏、测试难隔离作用域仅限单个进程更好的替代多数场景下是依赖注入合理的使用条件资源稀缺、基本无状态、确实全局唯一单例的核心风险不在「怎么写」而在「要不要写」。它把全局状态包装得很体面让人放松警惕。每次想用它之前先问一句我要的是唯一性还是只是懒得传参如果答案是后者依赖注入通常才是正确的方向。

相关新闻

搞懂Java集合框架:HashMap扩容、ArrayList与选型实战深度解析

搞懂Java集合框架:HashMap扩容、ArrayList与选型实战深度解析

刚看完一段线上事故的复盘,起因是开发同学用HashMap做缓存,却忽略了并发写入的问题,结果线上服务时不时卡顿,最后排查下来是扩容时产生了循环依赖。这类问题其实和“Java 集合框架”本身无关,而是因为你只记住了 API&a…

2026/10/12 7:24:17 阅读更多 →
肿瘤坏死因子受体TWEAKR:从信号通路到ADC靶向策略的核心解析

肿瘤坏死因子受体TWEAKR:从信号通路到ADC靶向策略的核心解析

我第一次系统梳理TNF家族受体的时候,对着UniProt上的TNFRSF12A条目发了十分钟的呆。这个名字又长又绕,中文直译过来就是“肿瘤坏死因子超家族受体TWEAKR”,可它在文献里更常以Fn14出现,检索时还能撞见CD266这个“马甲”。搞免疫的…

2026/10/12 7:24:17 阅读更多 →
Lance数据检索选型指南:阿里云Milvus向量湖实战

Lance数据检索选型指南:阿里云Milvus向量湖实战

1. 从标题拆解:为什么“数据检索选型”成了绕不开的坎“Lance 数据检索怎么选,当然阿里云 Milvus 向量湖”——这个标题第一次看到的时候,我脑子里冒出来的第一个念头是:又是一个被向量检索选型折磨过的同行。Lance 这个格式在近两…

2026/10/12 7:24:17 阅读更多 →

最新新闻

前驱图与AOE网:用同一张DAG看懂操作系统与数据结构中的时间形状

前驱图与AOE网:用同一张DAG看懂操作系统与数据结构中的时间形状

期中周的那个深夜,我左手压着操作系统的教材,右手摊开数据结构的复习讲义。一边是前驱图的判断题,一边是AOE网的关键路径大题,我本来只想把这两页一起看掉,省得来回翻书,结果越看越不对劲——怎么它们画出来…

2026/10/13 10:02:18 阅读更多 →
EM算法详解:从隐变量推断到高斯混合模型实战

EM算法详解:从隐变量推断到高斯混合模型实战

1. 这不是数学课,是机器学习里“猜谜游戏”的通关手册 你有没有遇到过这样的场景:训练一个高斯混合模型时,明明数据摆在那里,却不知道每个样本该属于哪个高斯成分;做隐变量建模时,手握观测数据X&#xff0c…

2026/10/13 10:02:18 阅读更多 →
UserCF推荐算法原理与Python工程实践指南

UserCF推荐算法原理与Python工程实践指南

1. 项目概述:为什么UserCF至今仍是推荐系统入门必修课“基于用户的协同过滤算法(UserCF)”这九个字,听起来像教科书里的一个章节标题,但在我带过的二十多个实际推荐项目中,它几乎出现在每一个从零搭建的冷启…

2026/10/13 10:02:18 阅读更多 →
RAG知识库问答系统‘查得到答不对’的根因与工程化解法

RAG知识库问答系统‘查得到答不对’的根因与工程化解法

简介:这是一套开箱即用的大语言模型RAG知识库问答系统实现方案,面向AI工程师、企业知识中台建设者及高校科研人员,解决私域文档智能问答中幻觉高、召回差、模型绑定强等核心痛点。资源包共937个文件,以484个Python后端逻辑与向量化…

2026/10/13 10:02:18 阅读更多 →
C#实现STEP文件解析器:从词法分析到B-Rep实体树构建

C#实现STEP文件解析器:从词法分析到B-Rep实体树构建

简介:面向计算机专业本科毕设的STEP文件解析器项目,基于C#实现STEP中性文件的元素识别、拓扑关系提取与自定义数据结构保存,并通过STL格式转换配合Three.js完成3D模型加载显示。压缩包共19个文件,大小仅35KB,包含9个C#…

2026/10/13 10:02:18 阅读更多 →
用层次分析法(AHP)科学决策就业选择

用层次分析法(AHP)科学决策就业选择

1. 为什么就业选择不能只靠“感觉”和“听说”刚带完一届毕业设计,有位A同学连续三周来找我聊就业方向:一会儿说“听说算法岗钱多”,一会儿又焦虑“听说现在卷得不行”,最后拿出一张密密麻麻的Excel表,列了8家公司的岗…

2026/10/13 10:01:17 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →