【问题标题】:Mypy doesn't recognize class decoratorMypy 无法识别类装饰器
【发布时间】:2021-10-22 17:54:41
【问题描述】:

问题

假设我想实现一个类装饰器,为现有类添加一些属性和功能。

特别是,假设我有一个名为HasNumber 的协议,我需要一个装饰器can_add,它添加了将HasNumber 类转换为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 装饰器的返回值(注释为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本身的限制?

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

【问题讨论】:

  • 我怀疑这是mypy 的缺点。 cast 抑制了对返回的东西不是 CanAdd 的任何担忧,但如果 add 实际上已添加到 cls 参数中,则没有静态保证。
  • 因此,mypy 无法确认Foo 具有add 属性,只是您说Foo(带有或不带有add 属性)可以是can_add的返回值。
  • 我对 mypy 的期望是它用can_add 的返回类型替换装饰类,即Type[CanAdd],而不关心实际实现的实际情况(因为我故意忽略它与cast)。

标签: python mypy typing


【解决方案1】:

cast 告诉mypy cls(有或没有add 属性)可以安全地用作can_add 的返回值。它确实保证协议成立。

因此,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))

【讨论】:

  • 是的,继承可能是这里最好的解决方案。也许未来会对类装饰器有更好的支持。 (作为补充说明,再次确认它可能是一个 mypy 限制,Pylance 正确键入了装饰类)
猜你喜欢
  • 2021-05-17
  • 2022-01-16
  • 2016-07-23
  • 2022-01-18
  • 2012-09-28
  • 2019-11-12
  • 2019-11-13
  • 2021-05-09
  • 1970-01-01
相关资源
最近更新 更多