如何解决Python带类型注解的日志装饰器mypy传参错误?
解决装饰器注入logger导致的mypy类型检查错误及优化方案
问题根源
你遇到的mypy错误,是因为被装饰函数的类型注解里声明了logger参数,但调用时没有传入——虽然装饰器会在运行时自动注入这个参数,但mypy无法识别装饰器的动态注入逻辑,因此认为调用缺少必要参数。
解决方案一:给logger参数添加可选默认值
最简单的修复方式是给logger参数设置Optional类型和默认值None,同时在函数内部用断言确保运行时logger已被注入:
修改后的common.py
import logging import os from typing import Optional from logger import log @log def is_directory_empty(path: str, logger: Optional[logging.Logger] = None) -> bool: # 断言确保装饰器已注入logger,避免运行时None assert logger is not None, "Logger must be injected by @log decorator" logger.info(f"Checking directory: {path}") # 业务逻辑示例 return len(os.listdir(path)) == 0
原理
mypy会识别到logger是可选参数,允许调用时不传;而断言则保证了运行时不会出现None的情况,兼顾类型检查和运行安全。
解决方案二:使用ParamSpec完善装饰器类型注解
如果需要更严格的类型检查,可通过typing.ParamSpec和TypeVar为装饰器添加精确的类型注解,让mypy理解装饰器会自动处理logger参数:
修改后的logger.py
import logging from functools import wraps from typing import TypeVar, Callable, ParamSpec # 定义参数和返回值的类型变量 P = ParamSpec("P") R = TypeVar("R") def log(func: Callable[P, R]) -> Callable[P, R]: logger = logging.getLogger(func.__name__) @wraps(func) def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: # 注入logger参数 kwargs["logger"] = logger return func(*args, **kwargs) return wrapper
修改后的common.py(使用关键字-only参数)
import logging import os from logger import log @log def is_directory_empty(path: str, *, logger: logging.Logger) -> bool: logger.info(f"Checking directory: {path}") return len(os.listdir(path)) == 0
原理
ParamSpec会保留原函数的参数签名,mypy能识别到装饰后的函数不需要传入logger——因为装饰器会自动注入这个关键字-only参数。
更优方案:将logger绑定为函数属性
避免参数注入带来的类型问题,更优雅的方式是把logger直接绑定到被装饰函数的属性上,函数内部通过访问函数属性获取logger:
修改后的logger.py
import logging from functools import wraps from typing import TypeVar, Callable, ParamSpec P = ParamSpec("P") R = TypeVar("R") def log(func: Callable[P, R]) -> Callable[P, R]: logger = logging.getLogger(func.__name__) # 将logger绑定为函数的属性 func.logger = logger @wraps(func) def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: return func(*args, **kwargs) return wrapper
修改后的common.py
import logging import os from typing import TYPE_CHECKING, cast from logger import log # 针对mypy的类型提示(可选) if TYPE_CHECKING: from typing import Any def _get_func_logger(func: Any) -> logging.Logger: return func.logger @log def is_directory_empty(path: str) -> bool: # 访问函数属性获取logger,若mypy报错则用cast强制类型转换 logger = cast(logging.Logger, is_directory_empty.logger) logger.info(f"Checking directory: {path}") return len(os.listdir(path)) == 0
优点
- 不污染函数签名,调用时无需关注logger参数
- 类型检查更清晰,mypy通过类型提示能识别函数属性的存在
- 代码结构更简洁,符合Python的"显式优于隐式"原则
内容的提问来源于stack exchange,提问作者winter
相关产品推荐
相关产品推荐

