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

顾星河
顾星河
发布时间:2026-10-10| 4人浏览过
原创

本文详细解析了Python中实现单例模式的四种常见方式:类装饰器、__new__方法、工厂函数以及functools.lru_cache。通过代码示例对比了各方案的优缺点,并探讨了在工程实践中如何平衡透明性、可控性与可测试性,最终给出避免过度设计的建议。

单例模式简介

单例模式(Singleton Pattern)的核心目标是确保一个类在整个应用程序生命周期中仅存在一个实例,并提供一个全局访问点。在实际开发中,这种模式常用于需要全局共享资源或状态的场景:

  • 数据库连接池:全局共享连接池,避免频繁创建和销毁连接的开销。
  • 日志记录器:避免重复打开文件句柄,确保日志写入的有序性和一致性。
  • 配置管理器:全局只读一份配置,保证所有模块读取到的配置信息一致。
  • 缓存实例:全局共享缓存对象,减少内存占用并提高数据访问效率。

Python实现单例的四种方式

在Python中,实现单例模式有多种途径,每种方式在语法优雅性、语义清晰度和使用便捷性上各有侧重。以下将逐一介绍四种常见实现方式。

1. 类装饰器方式

利用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

2. __new__ 方法方式

通过重写类的 __new__ 方法,在实例创建阶段进行控制。这种方式最直观,但需要注意 _instance 属性可能会暴露给外部,影响封装性。

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

3. 工厂函数方式

使用函数作为工厂,显式调用 singleton() 来获取实例。这种方式需要显式调用,但提供了更高的灵活性。

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")

4. functools.lru_cache 方式

利用Python标准库中的 functools.lru_cache,通过设置 maxsize=1 实现简单的单例缓存。这种方式内置且简单,适合快速实现。

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

优缺点对比与建议

在实际工程中,选择单例模式的实现方式需要权衡透明性、可控性和可测试性。以下是对各方案的推荐指数及分析:

  • 类装饰器:推荐指数四颗星。语法优雅,语义清晰,易于复用。
  • __new__ 方法:推荐指数两颗星。虽然直观,但 _instance 暴露可能导致封装性问题。
  • 单例函数:推荐指数三颗星。需要显式调用,但提供了更高的灵活性。
  • lru_cache:推荐指数四颗星。内置且简单,适合快速实现。

工程实践建议

在大多数情况下,显式获取单例的写法更加清晰,使用者能够明确知道类是全局共享的。这种方式提供了更好的可控性,可以灵活控制创建时机和方式,同时也更易于测试,方便进行 mock 和替换。

# ✅ 推荐:显式获取单例
_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 装饰器)容易造成使用者的误解,误以为每次 new 都是新实例。除非确定此类必须是单例类且不会造成误解,否则不推荐这种方式。

# ❌ 不推荐:把类变成单例,除非很确定此类必须是单例类,不会造成使用者误解
@singleton
class Database:
    ...

多年编程感悟

在多年的编程实践中,我们逐渐认识到,朴素的设计往往是最有效的。复杂的继承链会导致调试困难,动态修改类属性使得状态难以追踪,而魔法装饰器和元编程虽然炫技,却增加了理解和维护的代价。

# 曾经的我:要用单例模式!
@singleton
class ConfigManager:
    ...

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

总结

本文介绍了Python中实现单例模式的四种常见方式,并对比了各自的优缺点。类装饰器方式和 lru_cache 方式因其语法优雅和简单性被推荐为最佳实践。在工程实践中,显式获取单例比将类直接变成单例更好,能够提供更清晰的语义和更好的可控性。最终建议是,能用简单方式解决的问题,不要过度设计。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。