商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > Python实现单例模式的几种常见方式

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

  发布于2026-07-08 阅读(0)

扫一扫,手机访问

前言

最近在整理项目代码时,重新翻到了几年前写的单例模式实现。说实话,作为一个在编程路上摸爬滚打多年的开发者,回顾这些代码时还蛮感慨的——从最初跟着教程懵懂使用,到后来钻研各种“巧妙”的花式实现,再到现在,反而回归了最朴素的方式。

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

特点:装饰器语法本身非常优雅,使用时一目了然,类定义上加上一个 @singleton 就能搞定。

方式二:__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 作为类变量直接暴露在外。有篇文章中的实现就采用了这种方式,后来发现那个类根本不需要单例模式,使用者如果觉得有必要,完全可以显式自行单例化。

方式三:函数

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. 少歧义:团队里新人看到 DBConnect() 被调用两次,第一反应不会模棱两可

五、多年编程感悟

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

这句话不是凭空说的,翻翻自己过去写的代码,教训比比皆是:

当年觉得“巧妙”后来发现的问题
复杂的继承链调试时一头雾水
动态修改类属性难以追踪状态变化
魔法装饰器新人看不懂
元编程炫技给自己挖坑

从花哨到朴素的转变

# 曾经:非要上单例模式!
@singleton
class ConfigManager:
    ...

# 现在:简单就好
@lru_cache
def get_db():
    return Database()

简单、直白、不需要额外解释。这才是经得起时间检验的代码。

六、总结

要点回顾

  1. 装饰器方式是类单例实现中最推荐的一种(前提是你确实需要一个必须为单例的类)
  2. 显式获取单例,在绝大多数场景下,比把类本身变成单例要好得多
  3. lru_cache 是最简单省事的单例缓存方案,Python 内置,无需额外代码
  4. 朴素思维:能用简单方式解决的问题,别去搞那些花里胡哨的设计模式
本文转载于:https://www.jb51.net/python/3633100n3.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注