如何在 Python 项目中全局共享单个类实例?

来源:前端技术作者:小团团头衔:草根站长
导读:本期聚焦于小伙伴创作的《如何在 Python 项目中全局共享单个类实例?》,敬请观看详情。模块级变量在多线程初始化时可能重复创建对象,导致配置被覆盖。Python 的模块天然具备进程内单例特性, import 同一模块得到的对象始终相同。借助模块属性或 __new__ 控制实例化过程,可以把数据库连接、日志器这类资源集中管理。相比每次传参,全局共享单个类实例能减少重复开销,也避免状态不一致。下文结合元类与装饰器写法,说明如何安全地实现跨文件复用。

在 Python 工程里,经常会遇到这样一种需求:某个类的对象在整个程序运行期间只能有一个,比如全局配置管理器、数据库连接池或者日志处理器。如果到处都去 new 一个出来,不仅浪费资源,还容易出现状态不一致的问题。要让一个类实例在项目中被全局共享,本质上就是实现单例并提供一个统一的访问入口。

如何在 Python 项目中全局共享单个类实例?

为什么需要全局共享单个类实例

当项目规模变大以后,很多模块都要读取同样的配置或者写入同一个日志文件。假设每个模块都自己创建一个配置对象,一旦某个地方修改了配置,其他模块完全感知不到,这就造成了数据割裂。把实例做成全局唯一,所有模块拿到的都是同一个对象,任何修改都能即时生效。

另外,有些资源本身就不适合重复创建,例如数据库连接、线程池、Redis 客户端。频繁实例化不仅会拖慢启动速度,还可能把连接数打满。通过全局共享单个类实例,可以把这些昂贵资源的创建控制在一次以内,后面谁用谁拿,既省心又安全。

利用模块特性实现最简单共享

Python 的模块在第一次被 import 时执行一遍,之后解释器会缓存到 sys.modules 里,后续再 import 拿到的是同一个模块对象。因此,把一个类的实例直接写在模块顶层,这个实例天然就是进程内单例。

下面这段代码把配置类实例化后赋值给模块变量,别的文件只要 from config import settings 就能拿到同一个对象:

# config.py
class Settings:
    def __init__(self):
        self.debug = True
        self.language = 'zh_CN'

# 模块加载时创建一次,全局共享
settings = Settings()

# user.py
from config import settings

def print_lang():
    print(settings.language)

这种写法几乎没有额外代码,也最容易理解。缺点是类本身还能被别人手动再实例化,如果你希望从语法层面禁止重复创建,就需要更严格的约束。

使用 __new__ 方法控制实例化

在类里面重写 __new__,可以判断类属性里是否已经有实例,有就直接返回,没有才真正创建。这样无论谁调用类名,拿到的都是同一个对象。

示例中使用一个下划线开头的变量保存实例,并在 __new__ 里加锁,避免多线程同时进来创建出两个对象:

import threading

class Singleton:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self, value=None):
        # 防止重复初始化覆盖属性
        if not hasattr(self, 'value'):
            self.value = value

a = Singleton(10)
b = Singleton(20)
print(a is b)  # True
print(a.value) # 10,不会被 20 覆盖

这里要注意 __init__ 每次调用类都会执行,所以用了 hasattr 做保护,否则第二个调用者可能把第一个设的值改掉。双检锁写法在多线程下是安全的,但代码稍显啰嗦。

用元类统一约束所有单例类

如果项目里有好几个类都要做成单例,每次都写一遍 __new__ 太重复。可以定义一个元类,在创建类的时候注入单例逻辑,以后需要单例的类只要指定 metaclass 即可。

元类通过重写 __call__ 来控制类被调用时的行为,下面的例子演示了如何用一个元类让任意类变成全局唯一:

class SingletonMeta(type):
    _instances = {}
    _lock = threading.Lock()

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            with cls._lock:
                if cls not in cls._instances:
                    cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Logger(metaclass=SingletonMeta):
    def __init__(self):
        self.lines = []

    def log(self, msg):
        self.lines.append(msg)

log1 = Logger()
log2 = Logger()
print(log1 is log2)  # True

元类方案把单例逻辑集中起来,业务类写起来非常干净。不过元类对初学者不太友好,调试时调用栈会多一层,需要团队能接受这种写法。

装饰器方式实现全局共享

如果不想碰元类,也可以用装饰器把类包一层,在装饰器内部维护实例字典。被装饰的类自动变成单例,代码直观且复用方便。

下面这个装饰器支持不同类各自保存自己的实例,也加了线程锁:

def singleton(cls):
    instances = {}
    lock = threading.Lock()
    def get_instance(*args, **kwargs):
        if cls not in instances:
            with lock:
                if cls not in instances:
                    instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class Cache:
    def __init__(self):
        self.store = {}

c1 = Cache()
c2 = Cache()
print(c1 is c2)  # True

装饰器比元类轻量,阅读成本更低。但它会把原类的身份变成函数,有些依赖 isinstance 的类型检查要写成 isinstance(obj, Cache) 时得注意 Cache 已被替换,可以用 functools.wraps 或保留原类引用解决。

不同方案的选择建议

对于小型脚本或工具,直接用模块级变量最省事,也不需要理解复杂机制。中型项目推荐 __new__ 或装饰器,既能明确控制初始化,又不引入元类概念。大型框架级别代码如果单例类多,元类可以让约束统一,减少重复劳动。

无论选哪种方式,都要留意测试场景:单元测试里经常希望每个用例有干净环境,全局单例会导致状态串扰。可以在测试前重置实例属性,或把实例放在依赖注入容器里,按需替换,这样既能享受共享便利,也不牺牲可测性。

方案实现难度线程安全适用场景
模块变量天然安全简单配置、工具脚本
__new__ 方法加锁后安全单个核心类
元类加锁后安全多类需统一单例
装饰器加锁后安全灵活复用

总结

在 Python 项目中全局共享单个类实例,核心思路就是让实例化过程可控且只发生一次。语言层面的模块缓存、__new__ 拦截、元类接管和装饰器包装都能达成目标。实际落地时,结合团队熟悉度和业务复杂度来选,并处理好多线程与测试隔离,就能稳定地复用同一个对象。

Python单例模式全局共享修改时间:2026-08-02 04:15:35

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。