Python模块间共享资源的最佳实践
这种场景我太熟悉了——写可复用模块的时候,既想让入口脚本(比如你的main.py)统一管理资源,又不想让子模块和入口脚本绑定死,免得以后别的脚本导入子模块还要改一堆代码。你现在用set_logger的方法确实有点繁琐,我给你分享几个更Pythonic的方案,从简单到进阶都有:
一、先解决日志的特殊情况:利用logging的层级特性
你举的日志例子其实不用手动传递logger实例——Python的logging模块本身就是按命名空间层级继承配置的。
比如在main.py里:
import logging # 配置根日志(或者当前模块的logger) logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG) logger = logging.getLogger(__name__) logger.info('Hello world')
然后在my_module_1/a.py里,只需要这样写:
import logging # 用当前模块的命名空间获取logger,它会自动继承父级(包括根日志)的配置 logger = logging.getLogger(__name__) logger.info('Logging from a.py')
这样a.py里的logger会自动应用main.py里的配置,完全不用手动传递实例,这是logging模块设计好的特性,专门解决这种跨模块日志配置的问题。
二、通用资源的共享方案(数据库、缓存、配置等)
对于日志之外的资源,比如数据库连接、缓存实例、全局配置,我们需要更通用的方法,下面几种是常用的:
1. 轻量级全局资源容器(简单易实现)
不用第三方库,自己写一个简单的容器类来统一管理资源,避免每个模块都写set_xxx方法。
比如新建一个common.py:
class ResourceContainer: def __init__(self): self.logger = None self.db_conn = None self.cache = None # 创建全局单例容器 container = ResourceContainer()
然后在main.py里初始化资源并放入容器:
from common import container import logging import psycopg2 # 初始化日志 container.logger = logging.getLogger(__name__) logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG) # 初始化数据库连接 container.db_conn = psycopg2.connect("dbname=test user=postgres")
之后任何子模块(比如my_module_1/a.py)直接从容器里取资源即可:
from common import container def do_db_operation(): container.logger.info("Executing DB operation") cursor = container.db_conn.cursor() # 执行数据库操作...
这种方法比单个模块写set方法简洁很多,而且所有资源统一管理,也方便后续扩展。注意如果是多线程/多进程场景,要给容器的资源访问加锁,避免并发问题。
2. 工厂函数/构造器注入(最解耦,推荐)
如果想让子模块完全独立,不依赖任何全局状态,最好的方式是让子模块的函数或类接受资源作为参数,也就是依赖注入的基础形式。
比如my_module_1/a.py里:
class DataProcessor: def __init__(self, logger, db_conn): # 直接通过构造器注入依赖 self.logger = logger self.db_conn = db_conn def process_data(self): self.logger.info("Starting data processing") # 使用db_conn处理数据... def fetch_data(logger, db_conn): logger.info("Fetching data from DB") cursor = db_conn.cursor() # 查询数据...
然后在main.py里:
from my_module_1.a import DataProcessor, fetch_data import logging import psycopg2 # 初始化资源 logger = logging.getLogger(__name__) logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG) db_conn = psycopg2.connect("dbname=test user=postgres") # 注入资源到子模块的类/函数 processor = DataProcessor(logger, db_conn) processor.process_data() fetch_data(logger, db_conn)
这种方式的好处是子模块完全不知道main.py的存在,只关心自己需要的资源类型,测试的时候还可以传入mock的logger或db_conn,非常灵活。如果觉得每次传参数麻烦,可以在main里写一个工厂函数来封装资源注入。
3. 第三方依赖注入库(适合大型项目)
你提到的dependency_injector确实是个不错的选择,但它更适合资源多、依赖关系复杂的大型项目。如果你的项目比较小,或者不想给子模块的使用者强加外部依赖,那前面两种轻量级方案就足够了。
如果真的要用dependency_injector,它可以帮你管理资源的生命周期(比如单例、请求级实例),自动注入依赖,代码会更规整,但学习成本和复杂度会高一些,需要权衡。
三、总结最佳实践
- 对于日志这类有内置层级机制的资源,优先利用模块自身的特性,不用手动传递。
- 通用资源优先选择构造器/函数参数注入,解耦性最强,可测试性最好。
- 如果需要全局共享资源实例,且不想每次传递参数,用轻量级全局容器是简洁的选择,注意线程安全。
- 大型项目或复杂依赖场景再考虑使用成熟的依赖注入库,避免过度设计。
备注:内容来源于stack exchange,提问作者user2526586

