如何为装饰子类函数的装饰器添加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
相关产品推荐
相关产品推荐

