如何用Python注解缩小类型?解决MyPy对属性校验装饰器的报错
解决MyPy对属性校验装饰器的类型报错问题
问题分析
你遇到的两个MyPy报错原因如下:
- 属性访问报错:MyPy无法识别装饰器
guard已经确保了self._attrib非None,所以在guarded_function中仍认为_attrib是Optional[str],不允许调用upper()。 - 装饰器类型不匹配:普通
TypeVar无法准确捕获带self参数的Callable类型信息,导致MyPy认为返回的inner函数和输入的func类型不兼容。
解决方案
利用PEP612的ParamSpec和TypeVar可以完美解决这两个问题,同时通过类型断言让MyPy识别_attrib的非空状态:
完整修正代码
from typing import TypeVar, ParamSpec, Callable, cast class AttribIsNone(Exception): ... # 定义参数规格和返回值类型变量 P = ParamSpec("P") R = TypeVar("R") T = TypeVar("T", bound="Thing") # 绑定到Thing类,确保self是Thing实例 def guard(func: Callable[[T, *P.args], R]) -> Callable[[T, *P.args], R]: def inner(self: T, *args: P.args, **kwargs: P.kwargs) -> R: if self._attrib is None: raise AttribIsNone() # 类型断言,告诉MyPy此时_attrib已非None self._attrib = cast(str, self._attrib) return func(self, *args, **kwargs) return inner class Thing: _attrib: str | None @guard def guarded_function(self) -> str: return self._attrib.upper()
关键说明
- ParamSpec的使用:
P用来捕获被装饰函数除self外的所有参数,确保装饰器能准确传递参数类型信息,解决返回值类型不匹配的报错。 - 类型断言:在装饰器内部通过
cast断言self._attrib为str,让MyPy明确此时属性已非空,消除属性访问报错。 - 替代方案:类型守卫函数
如果不想用cast,可以定义一个类型守卫函数,让MyPy更优雅地识别非空状态:
def has_attrib(self: Thing) -> bool:
if self._attrib is None:
raise AttribIsNone()
return True
def guard(func: Callable[[T, *P.args], R]) -> Callable[[T, *P.args], R]:
def inner(self: T, *args: P.args, **kwargs: P.kwargs) -> R:
has_attrib(self)
return func(self, *args, **kwargs)
return inner
内容的提问来源于stack exchange,提问作者Richard Vodden
相关产品推荐
相关产品推荐

