You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 22:43:17