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

使用ContextDecorator的Timer作装饰器时,MyPy报'untyped decorator'错误如何解决?

解决跨文件导入ContextDecorator子类作为装饰器时的MyPy类型错误

问题场景

我实现了一个继承自ContextDecorator的Timer类,支持同时作为上下文管理器和装饰器使用:

class Timer(ContextDecorator):
    """Context manager which times block execution and
    adds results to APMContext instance"""

    def __init__(self, key: str, apm_context: Optional[APMContext] = None) -> None:
        self._key = key
        self._apm_context = apm_context

    def __enter__(self) -> None:
        self._start_time = time.monotonic()

    def __exit__(
        self,
        typ: Optional[Type[BaseException]],
        value: Optional[BaseException],
        traceback: Optional[types.TracebackType],
    ) -> None:
        duration = max(time.monotonic() - self._start_time, 0) * 1000.0
        log_metric(self._key, round(duration, 3), self._apm_context)

当在其他文件中导入这个类并用来装饰get_results函数时,MyPy抛出了类型错误:

results.py:52: error: Untyped decorator makes function "get_results" untyped  [misc]
        @apm.Timer(key='get_results')

但如果把Timer类的定义直接移到使用它的文件里,MyPy就不会报错,推测是跨文件导入时,MyPy无法正确识别基类ContextDecorator的类型声明。

解决方案

1. 为Timer类显式添加带类型注解的__call__方法

ContextDecorator的__call__方法默认缺乏完善的类型提示,跨文件导入时MyPy无法推断装饰器的类型信息。我们可以显式实现__call__方法并添加精确的类型注解,让MyPy能正确追踪被装饰函数的参数和返回值类型:

from typing import TypeVar, Callable, ParamSpec
import time
from contextlib import ContextDecorator
from types import TracebackType
from typing import Optional, Type, BaseException

# 定义参数和返回值的类型变量,保留被装饰函数的类型信息
P = ParamSpec("P")
R = TypeVar("R")

class Timer(ContextDecorator):
    """Context manager which times block execution and
    adds results to APMContext instance"""

    def __init__(self, key: str, apm_context: Optional[APMContext] = None) -> None:
        self._key = key
        self._apm_context = apm_context

    def __enter__(self) -> None:
        self._start_time = time.monotonic()

    def __exit__(
        self,
        typ: Optional[Type[BaseException]],
        value: Optional[BaseException],
        traceback: Optional[TracebackType],
    ) -> None:
        duration = max(time.monotonic() - self._start_time, 0) * 1000.0
        log_metric(self._key, round(duration, 3), self._apm_context)

    def __call__(self, func: Callable[P, R]) -> Callable[P, R]:
        # 调用父类的__call__逻辑,同时提供明确的类型约束
        return super().__call__(func)

2. 检查模块导入和MyPy配置

  • 确认导入语句正确,避免使用可能导致MyPy解析失败的相对导入方式
  • 确保包含Timer类的模块被添加到MyPy配置文件(如mypy.ini、pyproject.toml)的include列表中,未被exclude规则排除
  • 如果项目使用命名空间包,需确保MyPy能正确识别包的结构

3. 临时忽略错误(应急方案)

如果暂时无法修改代码,可以在装饰器所在行添加类型忽略注释来绕过错误,但这只是权宜之计,不推荐长期使用:

# type: ignore[misc]
@apm.Timer(key='get_results')
def get_results():
    # 函数实现逻辑

内容的提问来源于stack exchange,提问作者gmoss

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 06:15:47