必须使用@functools.wraps(func)包裹装饰器内层函数,否则__name__、__doc__、inspect.signature()等元数据将指向wrapper而非原函数,导致help()、ide提示、类型检查和单元测试失效。

装饰器里怎么加日志输出而不破坏原函数签名
直接用 @functools.wraps 包裹内层函数,否则 help() 或 IDE 提示会显示装饰器的签名而非原函数。不加这句,inspect.signature(func) 拿到的是装饰器内部的 wrapper 签名,单元测试或类型检查容易出错。
常见错误现象:用 logging.debug("called") 打印后发现 func.__name__ 变成 wrapper,参数提示全丢。
- 必须在装饰器内部函数前加
@functools.wraps(func) - 导入不能省:
import functools - 日志内容建议包含
func.__name__和args/kwargs(但注意不要直接打印敏感字段)
如何让日志记录支持不同级别和可配置开关
硬编码 logging.info 不灵活,应把日志级别、是否启用作为装饰器参数传入。否则每次改级别都要改装饰器源码,没法按函数粒度控制。
使用场景:调试时想看 DEBUG 级别入参,上线后只留 WARNING 以上;或者仅对数据库操作函数开启详细日志。
- 装饰器本身要返回闭包,例如
def log_calls(level=logging.INFO, enabled=True): - 内部再定义真正的装饰器函数,用
getattr(logging, level.upper())动态调用 - 加
if not enabled: return func快速短路,避免日志模块初始化开销
@log_calls(level="DEBUG", enabled=os.getenv("LOG_ENABLED"))
def fetch_user(user_id):
return db.get(user_id)
为什么不能在装饰器里直接用 logging.basicConfig
logging.basicConfig 只能调用一次,后续调用无效。如果每个装饰器都尝试配置,会导致日志输出重复、格式混乱,甚至丢失 handler。
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
典型错误现象:同一行日志打印两遍;时间戳格式不统一;文件日志写入失败但控制台还在输出。
- 日志配置必须在程序入口(如
if __name__ == "__main__")一次性完成 - 装饰器里只负责调用
logging.getLogger(__name__).log(...)或直接用logging.info - 若需独立 logger,用
logging.getLogger("myapp.api")显式获取,别依赖 root logger
带参数的函数如何安全记录入参和返回值
直接 str(args) 可能触发对象的 __str__ 副作用,或导致大对象(如 DataFrame、二进制流)序列化卡住。返回值同理,尤其涉及数据库连接、文件句柄时不能随便 print。
性能影响明显:对高频调用函数(如每秒千次的 API 校验),序列化参数可能吃掉 20%+ CPU 时间。
- 默认只记录
len(args)、type(args[0])等轻量信息 - 提供
log_args_repr=False开关,关闭时只打"args: (3, <class>)"</class> - 返回值记录加
try/except,防止repr()报错中断原逻辑 - 敏感字段(如
password、token)需自动过滤,可用白名单键名匹配
复杂点在于,日志不是越详细越好——得在可观测性和性能、安全性之间做取舍。比如记录 SQL 查询时,参数化后的语句要脱敏,但又不能漏掉占位符数量这种关键线索。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










