Mypy 无法识别 class 装饰器

Mypy doesn't recognize class decorator

问题

假设我想实现一个 class 装饰器,为现有的 class 添加一些属性和函数。

特别是,假设我有一个名为 HasNumber 的协议,我需要一个装饰器 can_add 来添加缺少的方法以将 HasNumber class 转换为 CanAdd.

class HasNumber(Protocol):
    num: int

class CanAdd(HasNumber):
    def add(self, num: int) -> int: ...

实施

我实现装饰器如下:

_HasNumberT = TypeVar("_HasNumberT", bound=HasNumber)


def can_add(cls: Type[_HasNumberT]) -> Type[CanAdd]:
    def add(self: _HasNumberT, num: int) -> int:
        return self.num + num

    setattr(cls, "add", add)

    return cast(Type[CanAdd], cls)


@can_add
class Foo:
    num: int = 12

错误

代码在我 运行 时工作得很好,但 mypy 出于某种原因对此不满意。

它给出错误 "Foo" has no attribute "add" [attr-defined],就好像它没有考虑 can_add 装饰器的 return 值(注释为 Type[CanAdd])。

foo = Foo()
print(foo.add(4))  # "Foo" has no attribute "add"  [attr-defined]
reveal_type(foo) # note: Revealed type is "test.Foo"

问题

在这个issue中,有人演示了一种用Intersection注释的方法。但是,没有 Intersection 有没有办法实现呢? (假设我不关心 Foo 中的其他属性,除了协议中定义的属性)

或者是mypy本身的限制?

没有解决我问题的相关帖子:

cast 告诉 mypy cls(有或没有 add 属性)可以安全地用作 [=15] 的 return 值=].它保证协议成立。

因此,mypy 无法判断 Foo 是否已被赋予 add 属性,只能判断是否可以使用 can_add 装饰器。 can_add 具有定义 add 属性的副作用这一事实对 mypy.

不可见

但是,您可以用直接继承替换装饰器,例如

class HasNumber(Protocol):
    num: int


_HasNumberT = TypeVar("_HasNumberT", bound=HasNumber)

class Adder(HasNumber):
    def add(self, num: int) -> int:
        return self.num + num

class Foo(Adder):
    num: int = 12

foo = Foo()
print(foo.add(4))