如何创建复用同一Logger实例的Python函数装饰器?该场景是否有现成设计模式?
咱们先解决核心问题:怎么确保整个项目里的Logger实例是同一个,同时用装饰器给函数加日志功能。其实你给的示例已经摸到门道了——Python的logging.getLogger()本身就会返回单例实例(logging模块内部维护了一个注册表,同名的logger只会创建一次),不过如果要更稳妥地控制实例唯一性,咱们可以用更显式的方式实现,再结合对应的设计模式。
一、正确实现:全局单例Logger的装饰器
首先,咱们可以用模块级单例或者显式单例类来确保Logger实例唯一。这里给两种实用的实现方式:
方式1:利用Python模块的天然单例特性
Python模块在首次导入时会被加载,后续所有导入都是引用同一个模块对象,所以直接在模块里定义Logger实例就行,简单又可靠:
import logging # 模块级初始化,只会执行一次,全局复用这个实例 logger = logging.getLogger('project_global_logger') # 统一配置日志格式、级别等(可以根据需求调整) logger.setLevel(logging.INFO) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s: %(message)s') console_handler = logging.StreamHandler() console_handler.setFormatter(formatter) logger.addHandler(console_handler) def log_action(log_string=''): def decorator(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) # 直接复用上面的全局logger实例 logger.info(f"函数 {func.__name__}: {log_string}") return result return wrapper return decorator # 使用示例 class MyClass: @log_action('Action started') def action(self): pass @log_action('独立函数执行完成') def my_func(): pass
不管你在项目的哪个模块导入这个装饰器,logger都是同一个实例,完全满足需求。
方式2:显式实现单例类管理Logger
如果你想更清晰地控制单例逻辑(比如防止意外修改logger实例),可以写一个单例类来封装Logger,明确告诉其他开发者这个实例是全局唯一的:
import logging class SingletonLogger: _instance = None def __new__(cls, logger_name='project_global'): if cls._instance is None: cls._instance = logging.getLogger(logger_name) # 统一配置日志 cls._instance.setLevel(logging.INFO) formatter = logging.Formatter('%(asctime)s - %(levelname)s: %(message)s') cls._instance.addHandler(logging.StreamHandler()) cls._instance.handlers[0].setFormatter(formatter) return cls._instance # 获取全局唯一实例 global_logger = SingletonLogger() def log_action(log_string=''): def decorator(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) global_logger.info(f"[{func.__name__}] {log_string}") return result return wrapper return decorator
二、对应的设计模式
针对这个场景,主要用到两种核心设计模式:
1. 单例模式(Singleton Pattern)
这是确保Logger实例全局唯一的关键。不管是用模块天然单例,还是显式单例类,本质都是单例模式的应用——保证一个类只有一个实例,并提供一个全局访问点。
在Python里,模块是最简便的单例实现方式,因为Python解释器会自动保证模块只被加载一次;显式单例类则更适合需要自定义单例逻辑的场景。
2. 装饰器模式(Decorator Pattern)
你的日志功能本身就是装饰器模式的典型应用:在不修改原函数代码的前提下,动态地给函数添加额外的日志记录功能,同时保持原函数的调用接口不变。这种方式让日志功能和业务逻辑解耦,你可以随时给任意函数加日志,也能轻松移除。
可选:依赖注入模式(Dependency Injection)
如果想让代码更灵活(比如后续需要替换Logger实现,比如换成文件日志或者第三方日志服务),可以结合依赖注入模式,把Logger实例作为参数传入装饰器:
def log_action(log_string='', logger=None): # 没传logger就用全局单例,传了就用传入的实例 logger = logger or global_logger def decorator(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) logger.info(f"[{func.__name__}] {log_string}") return result return wrapper return decorator
这样既保留了全局单例的默认行为,又支持灵活替换Logger,代码扩展性更好。
总结
要实现复用单一Logger实例的装饰器,核心是用单例模式确保实例唯一,再用装饰器模式给函数添加日志功能。Python的模块天然单例是最简便的实现方式,显式单例类则更显式可控,结合依赖注入还能提升代码灵活性。
内容的提问来源于stack exchange,提问作者Vyacheslav

