使用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
相关产品推荐
相关产品推荐

