装饰器读取debug状态应于运行时动态判断,优先用os.getenv("DEBUG")解析,配合functools.wraps保留函数签名;类装饰器易在初始化阶段固化环境值,导致行为异常。
关键不是“写个开关”,而是让装饰器能感知运行时环境。最直接的方式是检查 __debug__(Python 解释器内置标志,python -O 下为 False),或读取环境变量如 DEBUG。不建议硬编码布尔值,否则每次改逻辑都要动装饰器本身。
实操建议:
os.getenv("DEBUG", "").lower() in ("1", "true", "on"),和 Flask/Django 等主流框架行为一致settings.DEBUG,就直接引用它,避免多源头判断核心是“返回原函数”还是“返回包装函数”,取决于当前是否满足 debug 条件。不能只靠 if DEBUG: ... else: return func,因为那样会丢失原函数签名和元信息。
实操建议:
functools.wraps(func) 包裹包装函数,确保 help()、inspect.signature() 正常工作return func,而不是调用 func(*args, **kwargs) —— 否则会绕过所有装饰器链(比如你后面还套了个 @cache)import functools
import os
def debug_only(func):
DEBUG = os.getenv("DEBUG", 
"").lower() in ("1", "true", "on")
if not DEBUG:
return func
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"[DEBUG] Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
类装饰器看似结构清晰,但容易在 __init__ 阶段就提前读取 DEBUG,导致后续环境变量变化后装饰器行为不更新;或者忘记在 __call__ 中透传 *args, **kwargs,引发参数错误。
常见错误现象:
TypeError: __call__() takes 1 positional argument but 2 were given:没写 def __call__(self, *args, **kwargs)
__init__ 里读了 os.getenv,之后值就固定了__name__ 变成 "DebugOnly":没实现 __name__ = func.__name__ 或没用 functools.update_wrapper
Python 装饰器是从下往上执行的,所以 @debug_only 放在最外层(即写在最后一行)才能控制整个调用链。如果它被包在 @lru_cache 里面,那缓存逻辑永远先跑,debug 日志根本不会触发。
使用场景提醒:
@debug_only 单独加在它们上面@debug_only,改用 logging.getLogger().debug(...),由日志级别控制真正麻烦的是跨进程或子解释器场景——os.getenv 在 fork 后可能不同步,这时候得靠配置中心或显式传参,而不是依赖装饰器自动推断。