当前位置: 首页 > article >正文

[Python] Python中自带模块级的单例模式-不需要定义单例类

Python中的单例场景一般一些需要在模块中全局维护的变量变量修改范围在模块内简单方式是构建一个全局变量然后不符合编码规范1.线程安全与并发问题2.测试隔离困难3.缺乏多实例/多租户支持。一般做法用类面向对象的方式来实现因为符合 Google Style 并遵循面向对象和高内聚低耦合的代码原则。场景hermes-agent 工程中 skill_commands.py 就是使用全局变量来实现skill的读取 下面是改进建议和指导原则在分析agent/skill_commands.py文件中的全局变量设计时我们需要将其分为**不可变常量Immutable Constants和可变全局状态Mutable Global State**两类来讨论。在这段代码中以下两个是不可变常量编译后的正则表达式# Patterns for sanitizing skill names into clean hyphen-separated slugs. _SKILL_INVALID_CHARS re.compile(r[^a-z0-9-]) _SKILL_MULTI_HYPHEN re.compile(r-{2,})而以下两个是可变全局状态充当缓存_skill_commands: Dict[str, Dict[str, Any]] {} _skill_commands_platform: Optional[str] None下面我将从代码原则、Google Python 风格指南、潜在问题以及替代方案等维度详细解答您的问题。一、 这种处理方式好吗符合 Google Style 吗1. 对于不可变常量如_SKILL_INVALID_CHARS是好的处理方式且完全符合 Google Python 风格指南。原因预先编译正则表达式并将其声明为模块级常量可以避免在每次调用函数时重复编译从而提升性能。2. 对于可变全局状态如_skill_commands字典缓存不是最好的处理方式不完全符合 Google Python 风格指南。Google Style Guide 的明确指出“Avoid global variables.”避免使用全局变量“While they are occasionally useful, global variables should be avoided. They can cause unexpected behavior during imports… Instead, use class variables or pass them as parameters.”尽管全局变量偶尔有用但应尽量避免。它们可能会在导入期间导致非预期行为……相反应该使用类变量或将它们作为参数传递。原因Google 风格指南极力反对在模块级别使用**可变Mutable**的全局状态。这里的_skill_commands在运行时会被scan_skill_commands动态修改使用了global关键字这属于典型的可变全局状态。二、 这种设计违反了哪些代码原则潜在的 Bug 和隐患在现代软件工程中使用模块级可变全局变量主要存在以下几个严重隐患1. 线程安全与并发问题Thread Safety当多个线程或异步任务同时调用scan_skill_commands或reload_skills时它们会并发地修改同一个全局字典_skill_commands。由于 Python 字典的写操作不是天然线程安全的这可能会导致缓存数据损坏Data Corruption。竞争条件Race Conditions导致某个线程读取到不完整或正在被重写的缓存。2. 测试隔离困难Test Isolation全局变量会导致测试用例之间产生状态泄露State Leakage。如果测试 A 调用了scan_skill_commands写入了某些 Mock 数据而测试 B 没有重置该全局变量测试 B 就会受到测试 A 的干扰产生令人头疼的“幽灵测试失败”Flaky Tests。注正因为如此该项目在tests/_isolate_plugin.py中不得不实现了一套“每个测试启动一个独立子进程”的强制隔离机制这极大地增加了测试运行的开销就是为了对付这些全局状态带来的污染。3. 缺乏多实例/多租户支持Multi-instance / Multi-tenancy如果同一个进程内需要运行两个具有不同配置、不同 Profile例如用户 A 的 profile 和用户 B 的 profile的AIAgent实例由于它们共享同一个 Python 运行环境和模块全局变量它们会共用同一个_skill_commands缓存从而导致越权或配置冲突。三、 为什么不使用“类”或其他方式历史原因与考量既然有上述缺点为什么原作者还要用全局变量而不写成类呢通常有以下几个原因1. 历史遗留与 YAGNIYou Aren’t Gonna Need It起点简单Hermes CLI 最早可能只是一个简单的单进程、单线程命令行工具。在这种场景下模块级的全局变量就是最快、最省事的“单例Singleton”实现方式。避免过度设计在早期引入一个SkillRegistry类并实例化它可能显得代码过于臃肿。2. 伪单例的便利性Python 的模块Module导入机制本身就是一个天然的单例模式同一个模块在进程中只会被执行和加载一次。通过模块级变量其他地方只需要简单的from agent.skill_commands import get_skill_commands就能直接获取到全局共享的缓存不需要显式地传递 registry 对象实例。四、 更好的替代方案是什么为了符合 Google Style 并遵循面向对象和高内聚低耦合的代码原则可以采用以下几种更好的重构方案方案 A使用类面向对象与封装—— 最推荐将 Skill 缓存和管理逻辑封装进一个SkillCommandRegistry类中将状态保存在实例属性中classSkillCommandRegistry:def__init__(self):self._skill_commands:dict[str,dict[str,Any]]{}self._skill_commands_platform:Optional[str]Nonedef_resolve_platform(self)-Optional[str]:# 原来的 _resolve_skill_commands_platform 逻辑...defscan(self)-dict[str,dict[str,Any]]:# 扫描逻辑将结果写入 self._skill_commands...defget_commands(self)-dict[str,dict[str,Any]]:current_platformself._resolve_platform()ifnotself._skill_commandsorself._skill_commands_platform!current_platform:self.scan()returnself._skill_commands# 进程内默认单例skill_registrySkillCommandRegistry()优点测试友好在单元测试中每个测试可以简单地通过SkillCommandRegistry()创建全新的、完全隔离的实例无需担心状态泄露。多 Profile 支持如果有多个 Profile可以创建多个 registry 实例互不干扰。消除global关键字符合干净代码Clean Code原则。方案 B使用标准库缓存装饰器functools.lru_cache如果只是为了做缓存Python 提供了优雅的内置装饰器。但因为本文件中的缓存依赖于_resolve_skill_commands_platform()的动态平台切换单纯的lru_cache可能不够灵活但也可以通过自定义缓存类Cache Manager来管理。总结不可变正则常量是好的、符合 Google 规范的做法。可变全局缓存_skill_commands不符合 Google 规范在并发、测试、多 Profile 场景下存在隐患。建议在未来系统规模扩大、引入更多并发或需要支持更严格的多 Profile 隔离时应该将这些全局状态重构为类实例Class Instance Properties / Objects 或上下文环境对象Context Objects。SkillCommandRegistry 为什么没有设计成单例这是一个非常深刻且关键的设计问题简单来说在运行时它确实需要保持“单例的行为”但在代码实现上我们千万不要把它设计成“强行无法被创建第二个实例的‘严格单例’”。在 Python 的世界里有比传统设计模式如 Java 中的严格单例更好、更灵活的处理方式。以下为您深度剖析一、 Python 中最推荐的“天然单例”模块级单例Python 的模块导入机制本身就是天然的单例模式。当一个模块首次被导入时Python 会执行它并将它缓存在sys.modules中后续所有的import拿到的都是同一个模块对象。所以最符合 Pythonic 风格的单例实现方式是在文件末尾实例化它并直接导出该实例。# agent/skill_commands.pyclassSkillCommandRegistry:def__init__(self):self._skill_commands{}...# 在模块级别直接实例化一个全局共享的实例skill_registrySkillCommandRegistry()当其他模块需要使用时不直接调用SkillCommandRegistry()重新创建而是直接导入这个已经创建好的实例# 从其他文件导入这个“模块级单例”fromagent.skill_commandsimportskill_registry# 直接使用它这保证了在整个运行期大家都共用这同一个实例和它的缓存commandsskill_registry.get_commands()二、 为什么不要做成“严格单例”即强行限制只能实例化一次在 Java 或 C 中我们经常通过私有化构造函数或使用元类Metaclass来强行限制用户不能通过Registry()创建第二个实例。但在 Python 中这种严格单例Strict Singleton通常是一个设计陷阱原因有二1. 它是单元测试的“灾难”如果SkillCommandRegistry是严格单例那么在运行测试套件时几十个测试用例会共享这唯一一个实例。测试 A 往里面写入了 Mock 的 Skill 缓存。测试 B 运行的时候读到了测试 A 的数据导致测试 B 莫名其妙地失败。如果它是非严格单例即普通的类我们在单元测试中就可以非常轻松地为每一个测试用例创建一个全新的、干净的实例彻底避免测试状态污染# tests/test_skill_commands.pydeftest_scan_skills():# 每次测试都创建一个全新的、隔离的 registry测试结束自动销毁registrySkillCommandRegistry()registry.scan()assertlen(registry.get_commands())02. 它会锁死多 Profile多实例的扩展性正如 Hermes 项目中的规则所说Hermes 强烈支持Profiles多实例支持。用户可以运行hermes -p dev开发 Profile指向~/.hermes/profiles/dev目录有它专属的 skills 目录。也可以同时在另一个终端运行hermes -p prod生产 Profile指向~/.hermes/profiles/prod。如果在未来Hermes Gateway 升级为多租户架构在一个 Python 进程中同时服务多个不同的 Profile / 用户如果写成严格单例整个进程就只能有一个 Registry导致不同 Profile 之间的 Skills 发生越权混淆。如果是普通的类我们可以为每个 Profile 创建一个独立的实例完美隔离。三、 完美的架构设计将 Registry 绑定在 Context上下文中最优雅的架构设计既能保证运行时是单例又能保证测试和多 Profile 的隔离那就是将 Registry 的生命周期与AIAgent或HermesCLI实例进行绑定。也就是说不要使用任何全局的单例而是让AIAgent持有它# run_agent.py (伪代码)classAIAgent:def__init__(self,profile_namedefault,...):# 每一个 Agent 实例在初始化时创建自己专属的 Registry# 这对于这个 Agent 来说就是自然单例但不同 Agent 之间是隔离的self.skills_registrySkillCommandRegistry(profile_nameprofile_name)这样设计天然单例在一个AIAgent的生命周期内它只拥有一个skills_registry实例行为和单例完全一致。多实例隔离两个不同的AIAgent例如不同的用户或 Profile拥有各自的skills_registry互不干扰数据非常安全。极易测试测试时只需要实例化一个AIAgent测试结束直接销毁没有任何全局残留状态。总结需要单例的行为保证缓存只被加载一次全局共享。不需要严格的单例限制强行拦截__new__。最佳实践初期/中期在模块中声明class SkillCommandRegistry并在文件底部实例化skill_registry SkillCommandRegistry()。外部通过from ... import skill_registry共享该模块级单例。后期更优将SkillCommandRegistry的实例作为AIAgent或控制器的成员变量属性由上下文去管理它的生命周期。

相关文章:

[Python] Python中自带模块级的单例模式-不需要定义单例类

Python中的单例场景 一般一些需要在模块中全局维护的变量(变量修改范围在模块内);简单方式是构建一个全局变量,然后不符合编码规范:1.线程安全与并发问题;2.测试隔离困难;3.缺乏多实例/多租户支…...

CVPR 2019 RKD论文复现踩坑记:从理论公式到可运行的PyTorch代码全解析

CVPR 2019 RKD论文复现实战:从数学推导到工业级PyTorch实现的关键细节当我在实验室第一次尝试复现CVPR 2019的Relational Knowledge Distillation(RKD)算法时,原以为按照论文公式直接编码就能快速跑通实验。但实际动手后才发现&am…...

信号与系统避坑指南:为什么两个三角波卷积不是尖顶脉冲?用Python和傅里叶变换给你讲透

信号与系统深度解析:三角波卷积的数学本质与Python验证在信号与系统课程中,卷积运算是一个既基础又关键的概念。许多学习者第一次接触两个三角波卷积时,往往会直觉地认为结果应该是一个更"尖锐"的尖顶脉冲。这种直觉错误非常普遍&a…...

Gemini 3.5破解50年数学猜想,数学家紧急复核

AI 攻克人类智慧高地?Gemini 3.5 传出“破解 50 年数学猜想”重大突破,数学家:正在紧急复核!2026年伊始,科技界与学术界共同迎来了一场堪称“地震级”的重磅新闻。据权威学术预印本网站及谷歌 DeepMind 团队透露&#…...

别再为乱码头疼了!Linux离线安装LibreOffice 7.5完整指南:从RPM包到完美中文显示

Linux离线安装LibreOffice 7.5终极指南:彻底解决中文乱码难题 在Linux环境下处理中文文档时,字体显示问题就像一场无声的战争——你永远不知道打开文件时会遭遇怎样的"乱码突袭"。特别是对于需要离线安装LibreOffice的用户,这个问题…...

从零开始手搓一个xv6内核页表:跟着6.S081源码一步步理解walk和mappages函数

从零构建xv6内核页表:深入解析walk与mappages的RISC-V实现在操作系统的核心机制中,虚拟内存管理始终是最具挑战性的部分之一。当我们打开MIT 6.S081课程的实验手册,面对"实现一个简化版页表"的任务时,许多学习者会陷入理…...

2026 中国 GEO 优化定制技术解析:企业资质代办的核心作用深度测评

随着生成式人工智能技术的快速普及,大语言模型已成为企业获取线上流量、塑造品牌认知的核心渠道。GEO(Generative Engine Optimization,生成引擎优化)作为 AI 时代的新兴优化领域,正在重构企业的线上可见性竞争规则。然…...

合肥Geo搜索优化服务的真实成本与效果分析

这两年,“AI搜索优化”、“GEO(生成式引擎优化)”在中小企业的朋友圈里反复刷屏。我身边不少安徽本土的老板,尤其是做教培、法律和机械制造的,从去年底就开始频繁问我:“这玩意儿到底靠不靠谱?投…...

从技术配置角度拆解全屋定制:五金件选型对柜体长期稳定性的影响

装修做全屋定制,大部分人的关注点集中在板材的环保等级和封边工艺上。但在日常使用中,决定一套柜子用起来顺不顺滑、耐不耐用的关键因素,还有一项容易被忽略——五金件的选型与安装精度。作为一个习惯把东西拆开研究明白的人,这次…...

安全稀疏矩阵乘法:基于二叉树递归传播的MPC算法优化详解

1. 项目概述:当稀疏矩阵乘法遇上安全多方计算 在分布式机器学习、联合数据分析以及隐私保护推荐系统的构建中,我们常常面临一个核心矛盾:数据的所有权分散在多个互不信任的参与方手中,大家希望共同训练一个模型或进行一次计算&…...

2026年5月儿童护眼灯品牌推荐:TOP5排名书桌防蓝光评测

摘要 当儿童近视率持续攀升,家长在选购护眼灯时面临从“照亮”到“护眼”的认知升级,如何在琳琅满目的品牌中锁定真正科学有效的方案成为核心焦虑。根据世界卫生组织最新数据,全球儿童近视患病率预计在2050年将达到50%,而照明环境…...

祖玛游戏开发:状态机与路径拓扑的工程实践

1. 祖玛游戏到底在考什么:不是炫技,而是对状态机与碰撞逻辑的精准拿捏祖玛(Zuma)看起来只是几颗彩球连成线就爆炸的休闲游戏,但真正动手实现时,你会发现它像一块试金石——C#、C 和 Java 三门语言各自最常被…...

FPGA与机器学习协同加速量子点自动调谐:原理、实现与性能分析

1. 项目概述:当FPGA遇上机器学习,量子点调谐的“自动驾驶”时代在量子计算实验室里,调谐一个量子点器件进入单电子态,是每个实验物理学家都绕不开的“苦差事”。这活儿有多磨人?你得坐在仪器前,手动调节两个…...

c++ csv?_?C++处理csv文件格式的fstream与字符串分割方法详解.txt

...

SQL like 与 正则 区别

SQL 中的 LIKE 和正则表达式(REGEXP 或 RLIKE)都用于模式匹配,但它们在表达能力、语法复杂度、性能上有显著区别。核心区别一览表对比维度LIKE正则表达式匹配粒度通配符(%、_)元字符、量词、字符类等表达能力弱&#x…...

uWSGI目录穿越漏洞CVE-2018-7490深度利用与防御

1. 这不是“文件读取”那么简单:uWSGI目录穿越漏洞的真实杀伤半径你可能在Vulfocus靶场里点开CVE-2018-7490这个靶机,输入/..%2f..%2f..%2fetc%2fpasswd,页面返回了一堆用户名,然后就关掉了——觉得“哦,能读文件&…...

JavaScript 高频基础面试题

在前端面试与日常开发中,JavaScript 基础语法、数组操作、循环、函数、定时器等知识点是必考、必用的核心内容。我整理了从 41 到 52 题的高频经典题目,搭配标准回答 代码示例 核心要点,逻辑清晰、面试直接背诵,一篇搞定基础通关…...

C语言基础 内存管理

第十章 内存管理./a.out运行起来后,系统会给a.out分配一段内存区域1 code 存放编写好的c语言代码。只读特性,在运行期间不能修改。2 data 数据段。存储全局变量,以及被static修改的变量。细分:data 数据段,有初值的…...

01-大模型AI:大模型学习指南

大模型概述 一、大模型训练的三大核心阶段 预训练:自监督学习的“知识积累期” 预训练是大模型的“启蒙阶段”,采用自监督学习模式。模型像海绵一样从海量文本数据中自主学习语言规律、语义关联和世界知识。例如,训练一个AI领域大模型时,会输入数百万篇AI论文、技术博客…...

用 AI 生成接口文档和测试用例:比“问一句答一句”更适合程序员的会员用法

很多程序员不是不愿意写接口文档,也不是不知道测试用例重要,而是这些事情经常被排在最后。 功能要赶,Bug 要修,需求还在改。等接口基本稳定以后,文档往往已经落后,测试用例也只覆盖了几个最常见路径。最后…...

SSH、SNMP、NETCONF、SFTP

SSH CE12800配置 #开启SSH服务 stelnet server enable ssh user renxinyu ssh user renxinyu authentication-type password ssh user renxinyu service-type stelnet #创建本地用户 aaalocal-user renxinyu password cipher Huawei123local-user renxinyu level 3local-user r…...

抖音a_bogus生成原理与Python逆向实现全解析

1. 为什么a_bogus成了抖音自动化绕不开的“铁门栓”你写了个脚本,模拟用户行为去抓取抖音的视频列表、评论或用户主页数据,请求发出去,返回的却是{"status_code": 10111, "status_msg": "invalid a_bogus"}——…...

深入理解RAG中的嵌入模型Embedding Model

前言在当前流行的RAG引擎(例如RAGFlow、Qanything、Dify、FastGPT等)中,嵌入模型(Embedding Model)是必不可少的关键组件。在RAG引擎中究竟扮演着怎样的角色呢?本文笔者进行了总结,与大家分享~什…...

麒麟系统启动卡住别慌!这可能是磁盘文件系统坏了,试试这几条Linux命令自救

麒麟系统启动卡顿故障排查指南:从原理到实战的磁盘修复方案 当你的麒麟系统突然卡在启动界面,屏幕上只留下"Boot From Harddisk"或EFI stub信息时,那种焦虑感我深有体会。作为一名经历过数十次类似故障排查的技术顾问,我…...

2026年免费照片去水印软件App推荐,一看就会的保姆级详细教程

你是不是也遇到过这样的场景:好不容易在网上看到一张心水的壁纸、一张有趣的表情包,或者自己拍的视频截图里有碍眼的日期戳、平台logo,想拿来发朋友圈,结果那个水印就像一块顽固的“牛皮癣”,怎么都去不掉?…...

12周学习笔记

...

2026年照片去水印免费软件保姆级教程!学会这几招,告别水印烦恼

你是不是也遇到过这样的抓狂时刻?在平台上刷到一张特别适合做壁纸或配图的高清照片,兴冲冲地保存下来,结果角落里的水印瞬间让整张图的格调打了对折;又或者,自己辛辛苦苦做好的图片,在分享转发几道后&#…...

13.解决 99% 刷机故障!小米 / 华为 / OV / 苹果通用救砖与分区修复教程

摘要 本文面向具备基础电子知识的技术人员,系统阐述主流品牌手机(华为、小米、OPPO、vivo、一加、苹果)的刷机与维修全流程。内容涵盖底层引导加载机制、分区表结构、签名验证原理,并提供完整的刷机脚本与维修诊断工具链。所有代码均已测试,可直接在Linux/Windows环境下运…...

室内点云轮廓提取

1 简介 室内点云轮廓提取是三维感知中的一项基础处理技术,它的核心作用是将杂乱、海量的原始点云,转化为简洁、有意义的几何边界。主要用处体现在以下几个方面: 1 机器人导航与避障 轮廓提取能实时勾勒出墙壁、家具、门窗等障碍物的边缘,帮助扫地机器人、服务机器人快速理…...

离线的银河麒麟系统部署ollama

一、概述 在离线的银河麒麟系统进行开发工作,总会遇到一些简单琐碎的问题,并且一些算法或者需要导入或者需要手敲,是一件很折磨的事。因此在服务器本地部署大模型,十分有必要。 二、部署方案 采用 docker ollama qwen2.5-code…...