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

如何用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()

关键说明

  1. ParamSpec的使用:P用来捕获被装饰函数除self外的所有参数,确保装饰器能准确传递参数类型信息,解决返回值类型不匹配的报错。
  2. 类型断言:在装饰器内部通过cast断言self._attrib为str,让MyPy明确此时属性已非空,消除属性访问报错。
  3. 替代方案:类型守卫函数
    如果不想用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
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 14:35:39