当前位置:

首页 > 编程开发 > Python单例模式的几种实现方式

Python单例模式的几种实现方式

前言 最近在整理项目代码,又一次审视了单例模式的各种实现。说来有趣,对单例模式的理解,似乎走过了一个完整的循环:从最初懵懂地套用,到后来热衷于追求各种“巧妙”甚至“炫技”的写法,最后兜兜转转,还是觉得最朴素的方案最可靠。 今天,我们就来系统梳理一下Python中实现单例的几种主流方式,并分享一个在编

本文目录

    前言

    最近在整理项目代码,又一次审视了单例模式的各种实现。说来有趣,对单例模式的理解,似乎走过了一个完整的循环:从最初懵懂地套用,到后来热衷于追求各种“巧妙”甚至“炫技”的写法,最后兜兜转转,还是觉得最朴素的方案最可靠。

    Python单例模式的几种实现方式

    今天,我们就来系统梳理一下Python中实现单例的几种主流方式,并分享一个在编程圈子里逐渐形成的共识:很多时候,朴素的就是最好的。

    一、单例模式简介

    单例模式(Singleton Pattern),这个设计模式的核心目标很明确:确保一个类只有一个实例,并且提供一个全局的访问点。听起来简单,但用起来却有不少门道。

    适用场景

    场景 说明
    数据库连接 全局共享一个连接池,避免重复建立连接的开销
    日志记录器 确保所有日志写入同一个文件或流,避免重复打开文件
    配置管理器 全局只读一份配置,保证数据一致性
    缓存实例 全局共享缓存,提升访问效率

    二、四种实现方式

    那么,在Python的世界里,有哪些方法可以实现单例呢?下面这四种,算是比较有代表性的。

    方式一:类装饰器

    def singleton(cls):
        """单例装饰器:闭包保存唯一实例"""
        _instance = None
    
        def wrapper(*args, **kwargs):
            nonlocal _instance
            if _instance is None:
                _instance = cls(*args, **kwargs)
            return _instance
    
        return wrapper
    
    
    @singleton
    class DBConnect:
        def __init__(self, host):
            self.host = host
            print("数据库连接初始化")
    
    
    # 使用
    obj1 = DBConnect("127.0.0.1")
    obj2 = DBConnect("192.168.1.1")  # 不会重新初始化
    
    print(obj1 is obj2)  # True
    print(obj1.host)     # 127.0.0.1
    

    特点:装饰器的语法非常优雅,使用时一目了然,算是Pythonic的实现之一。

    方式二:__new__方法

    class MySingleton:
        """单例类:通过 __new__ 控制实例创建"""
        _instance = None
    
        def __new__(cls):
            if cls._instance is None:
                cls._instance = super().__new__(cls)
            return cls._instance
    
    
    # 使用
    obj1 = MySingleton()
    obj2 = MySingleton()
    print(obj1 is obj2)  # True
    

    特点:这是最直接、教科书式的方式。但有个小问题,_instance作为类变量暴露在外。话说回来,之前在一篇关于Playwright封装的文章里就用了这种方式,现在回头看觉得不太妥——那个类本身并不必须是个单例。更好的做法是,把单例化的选择权交给使用者,让他们根据需要显式处理。

    方式三:函数

    from typing import Type, TypeVar
    
    T = TypeVar('T')
    
    def singleton(cls: Type[T], *args, **kwargs) -> T:
        """单例工厂函数"""
        if not hasattr(cls, '_instance'):
            cls._instance = cls(*args, **kwargs)
        return cls._instance
    
    
    # 使用
    class TargetClass:
        def __init__(self, config):
            self.config = config
    
    obj = singleton(TargetClass, config="value")
    

    特点:需要显式调用singleton()函数。优点是语义非常明确,使用者清楚地知道自己在获取一个单例;缺点嘛,就是每次都要多写几个字符,不够直观。

    方式四:functools.lru_cache

    from functools import lru_cache
    
    @lru_cache(maxsize=1)
    def get_instance():
        return MyClass(arg1="value")
    
    
    # 使用
    obj1 = get_instance()
    obj2 = get_instance()
    print(obj1 is obj2)  # True
    

    特点:直接利用Python标准库,无需额外造轮子。虽然从语义上说它更像一个缓存,但实现单例的效果是杠杠的,而且还能享受缓存机制带来的其他好处。

    三、优缺点对比

    光看代码可能还不够直观,我们把几种方式放到一起对比一下,优劣就清晰了。

    实现方式 优点 缺点 推荐指数
    类装饰器 语法优雅、语义清晰、易于理解 对于非必要实现单例的类,对使用者会造成困扰 ⭐⭐⭐⭐
    new 方法 最直观、最简单 实例变量暴露、可复用性不好 ⭐⭐
    单例函数 显式调用、可控性强,使用者清楚知道正在做什么 每次都要调用、不够直观 ⭐⭐⭐
    lru_cache 内置、无需额外代码 语义上不是单例、更像缓存,但是胜在简单 ⭐⭐⭐⭐

    四、我的建议

    看了这么多实现,到底该怎么选呢?这里有一个核心建议。

    优先使用显式单例

    相比费尽心思把一个类本身变成单例,显式地获取单例往往是更清晰的写法。来看两个例子:

    # ✅ 推荐:显式获取单例
    _db = None
    def get_db():
        global _db
        if _db is None:
            _db = Database()
        return _db
    
    # 或者更简洁地用 lru_cache
    @lru_cache
    def get_db():
        return Database()
    
    # ❌ 不推荐:把类变成单例,除非很确定此类必须是单例类,不会造成使用者误解
    @singleton
    class Database:
        ...
    

    为什么?

    1. 透明性:使用者能一眼看出这个对象是全局共享的,没有隐藏的魔法。
    2. 可控性:创建时机、创建方式完全由你掌控,灵活性更高。
    3. 易测试:在单元测试中,Mock或者替换一个函数返回的实例,比替换一个单例类本身要容易得多。
    4. 少歧义:避免了使用者误以为每次调用构造函数都会得到一个新实例的尴尬。

    五、多年编程感悟

    朴素的就是最好的。所有当时自鸣得意的所谓编程技巧,最后都会被证明要用更多的代价来理解和维护它。

    这句话可能道出了很多资深开发者的心声。追求“巧妙”有时反而会引入不必要的复杂度。

    几个教训

    当年觉得"巧妙" 后来发现的问题
    复杂的继承链 调试时一头雾水,方法调用路径像迷宫
    动态修改类属性 程序状态难以追踪,行为不可预测
    魔法装饰器 团队新人看得云里雾里, onboarding 成本高
    元编程炫技 往往是给自己或后人挖坑, debug 如同解谜

    回归朴素

    代码的演进,常常是一个从复杂回归简单的过程。

    # 曾经的我:一定要用上单例模式这个“高级”特性!
    @singleton
    class ConfigManager:
        ...
    
    # 现在的我:简单、直接,往往就是最好的
    @lru_cache
    def get_db():
        return Database()
    

    简单、直白、不需要额外的解释。这样的代码,才是真正的好代码。

    六、总结

    最后,我们来回顾一下今天的要点。

    要点回顾

    1. 装饰器方式:如果你确定某个类必须且永远是单例,那么类装饰器是实现方式中最推荐的一种。
    2. 显式获取单例:在大多数情况下,通过一个函数显式地获取单例实例,比把类本身设计成单例更清晰、更灵活。
    3. lru_cache:对于简单的单例需求,functools.lru_cache 是最轻量、最Pythonic的缓存式解决方案。
    4. 朴素思维:这是最关键的一点。能够用简单方式解决的问题,就不要过度设计。代码的可读性和可维护性,永远应该排在“炫技”的前面。
    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    GitHub六个AI开源项目能做什么?软件理解、语音与求职场景
    GitHub六个AI开源项目能做什么?软件理解、语音与求职场景

    解析六个GitHub AI开源项目的具体用途,包括软件静态分析、智能体回答检查、多语言配音、求职材料生成、本地代码检索及动态歌词展示,帮助开发者根据实际需求筛选工具并正确验证结果。

    opencode安装skills教程及本地配置方法
    opencode安装skills教程及本地配置方法

    遇到OpenCode Skills无法加载或配置无效?本文手把手教你完成Skills的安装、环境变量设置及本地调试,避开常见坑点,让AI助手更懂你的代码习惯。

    如何开启Visual Studio2022实时代码分析警告提示
    如何开启Visual Studio2022实时代码分析警告提示

    解决 Visual Studio 2022 编辑时不显示代码分析警告的问题。指南涵盖 .NET 项目实时分析设置、Roslyn 分析器范围调整及错误列表来源筛选,确保开发者能高效利用实时代码诊断功能。

    如何在Visual Studio2022查看变量监视窗口调试程序
    如何在Visual Studio2022查看变量监视窗口调试程序

    详解 Visual Studio 2022 调试技巧:包括断点设置、监视窗口开启方法、变量添加步骤以及通过 F10 单步执行观察变量值变化的全过程。

    Visual Studio2017设置默认编码为UTF-8不带BOM
    Visual Studio2017设置默认编码为UTF-8不带BOM

    针对 Visual Studio 2017 缺乏全局默认编码选项的情况,详解如何利用 .editorconfig 设定项目级 UTF-8 无 BOM 规则,以及手动转换现有文件并校验字节的方法。

    Visual Studio2022F5调试提示启动项目未配置怎么修复
    Visual Studio2022F5调试提示启动项目未配置怎么修复

    解决 Visual Studio 2022 F5 调试报错问题,通过设置启动项目、检查解决方案属性及正确区分类库与应用项目,快速恢复调试功能。

    Visual Studio2022NuGet包还原失败离线处理方案
    Visual Studio2022NuGet包还原失败离线处理方案

    解决Visual Studio 2022离线环境中NuGet包还原失败的难题,涵盖依赖链分析、本地源配置、包集收集及隔离验证步骤,帮助开发者构建稳定的离线开发环境。

    Visual Studio2017大项目加载缓慢,优化加载性能技巧
    Visual Studio2017大项目加载缓慢,优化加载性能技巧

    解决Visual Studio 2017大型项目加载慢的实用指南。涵盖性能监测、扩展管理、文档恢复优化及版本兼容性检查,帮助开发者精准定位瓶颈并提升工作效率。

    Visual Studio2022设置多启动项目同时调试运行
    Visual Studio2022设置多启动项目同时调试运行

    详解 Visual Studio 2022 多启动项目配置方法,包括启动动作选择、顺序调整及调试技巧,帮助开发者高效调试分布式或前后端分离应用。

    fastapi下载文件接口实现教程
    fastapi下载文件接口实现教程

    深入解析在FastAPI框架中实现文件下载接口的完整方案,涵盖基于FileResponse的本地静态文件传输、基于StreamingResponse的分块流式传输、RFC 5987中文文件名编码规范、路径穿越安全防御及BackgroundTasks临时文件清理,并配备详细代码示例与架构解析图。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。