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

如何为装饰子类函数的装饰器添加mypy类型注解?

解决unittest装饰器的Mypy类型兼容问题

你遇到的这个报错确实是mypy对函数参数逆变检查的合理结果——SomeTest是unittest.TestCase的子类,但装饰器定义里要求的是接受TestCase实例的函数,而实际传入的是接受SomeTest实例的函数,mypy默认不允许这种逆变转换(因为子类实例比父类实例拥有更多属性,父类类型的函数无法保证能处理子类的额外属性)。

好在我们可以用泛型TypeVar来优雅解决这个问题,完全不需要用Any来绕过类型检查。下面是具体的实现方案:

步骤1:导入泛型工具并定义绑定的TypeVar

首先,我们需要定义一个被限制为unittest.TestCase子类的类型变量,这样装饰器就能适配所有测试用例子类:

import unittest
from typing import Callable, TypeVar

# 定义一个绑定到TestCase的类型变量,只能是TestCase或其子类
T = TypeVar('T', bound=unittest.TestCase)

class User:
    # 假设这是你的User类实现
    pass

步骤2:修改装饰器的类型提示

用刚才定义的T替换原来的unittest.TestCase,让装饰器的函数参数和内部的self都使用这个泛型类型:

def login_decorate(func: Callable[[T], None]) -> Callable[[T], None]:
    def decorated_function(self: T):
        self.user = User()  # 这里mypy会知道self是TestCase子类,拥有user属性
        return func(self)
    return decorated_function

def anonymous_decorate(func: Callable[[T], None]) -> Callable[[T], None]:
    def decorated_function(self: T):
        self.user = None
        return func(self)
    return decorated_function

步骤3:测试用例的使用

现在你的测试类可以正常使用装饰器,mypy会正确推断self.user的类型:

class SomeTest(unittest.TestCase):
    @login_decorate
    def test_login(self):
        reveal_type(self.user)  # mypy会输出: Revealed type is "__main__.User"
        # 这里可以安全地调用User类的方法,不会有类型报错

    @anonymous_decorate
    def test_anonymous(self):
        reveal_type(self.user)  # mypy会输出: Revealed type is "None"
        # 这里如果尝试调用User的方法,mypy会立刻报错,符合预期

为什么这个方案有效?

通过绑定到TestCase的TypeVar,我们告诉mypy:这个装饰器接受的函数可以是任何接受TestCase子类实例的函数,返回的装饰后函数也会保持这个子类类型。这样既满足了类型安全,又让装饰器能灵活适配不同的测试用例类。

运行mypy检查时,你会发现原来的报错消失了,同时reveal_type能准确显示self.user的类型,完全不需要用Any来妥协。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:12:35