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

发布日期 :2026-09-09 08:24:17 UTC

作者 :xuzhiping

访问量: 10 次浏览

一、单例模式简介

单例模式(Singleton Pattern)属于创建型设计模式,目标是保证一个类在程序全局仅有唯一实例对象,并对外提供统一访问入口。

典型适用业务场景

1.数据库连接:全局复用同一个连接 / 连接池,避免频繁创建销毁连接;

2.日志记录器:防止重复打开日志文件句柄;

3.配置管理器:全局加载一份配置信息;

4.公共缓存对象:全局共享缓存实例。

二、四种实现方案(附代码、特点)

方式 1:类装饰器实现单例

利用闭包保存唯一实例,使用装饰器语法修饰目标类。

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__ 负责对象实例的创建,重写该方法控制只生成一份实例。

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对外暴露,复用性一般,多线程并发下不安全。

方式 3:单例工厂函数(显式调用)

不修改类本身,通过工厂函数获取唯一实例。

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 内置缓存工具,缓存函数返回的对象,间接实现单例效果。

from functools import lru_cache


class MyClass:
    def __init__(self, arg1):
        self.arg1 = arg1


@lru_cache(maxsize=1)
def get_instance():
    return MyClass(arg1="value")


# 测试
obj1 = get_instance()
obj2 = get_instance()
print(obj1 is obj2)  # True

特点:标准库内置,不需要手写额外装饰器;本质是缓存,不是严格意义的单例模式,但代码极简。

三、工程实践建议:优先显式获取单例

核心观点:尽量不要把类本身改造为单例类;推荐写专门函数来获取全局实例,代码更透明,便于单元测试 Mock。

推荐写法:显式获取全局实例

# 写法1:原生全局变量懒加载
_db = None
def get_db():
    global _db
    if _db is None:
        # 模拟数据库类
        class Database:
            pass
        _db = Database()
    return _db


# 写法2:搭配 lru_cache,更加简洁
from functools import lru_cache

@lru_cache
def get_db():
    class Database:
        pass
    return Database()

不推荐:随意给类加上@singleton装饰器。除非业务 100% 确定这个类必须全局唯一,否则会带来隐性歧义,阅读代码的人不知道调用类构造器返回的永远是同一个对象。