如何注释可以实现为 属性 的属性?

How to annotate attribute that can be implemented as property?

我正在努力让 mypy 对我的类型注释感到满意。这是最小的例子:

class FooInterface:
    x: int


class FooWithAttribute(FooInterface):
    x: int = 0


class FooWithProperty(FooInterface):
    @property
    def x(self) -> int:
        return 0

根据我的理解,一切都很好:FooWithAttribute().xFooWithProperty().x 都将 return 0int,没有类型错误。然而 mypy 抱怨:

error: Signature of "x" incompatible with supertype "FooInterface"

有没有办法告诉mypy一切正常?现在我发现的唯一方法是在 FooInterface 中注释 x: typing.Any,这会浪费 x 是 int 的信息。

Mypy 实际上指出了您程序中的一个合法错误。为了演示,假设您有一个如下所示的程序:

def mutate(f: FooInterface) -> None:
    f.x = 100

看起来不错,对吧?但是如果我们这样做 mutate(FooWithProperty()) 会发生什么? Python 实际上会因 AttributeError!

而崩溃
Traceback (most recent call last):
  File "test.py", line 19, in <module>
    mutate(FooWithProperty())
  File "test.py", line 16, in mutate
    f.x = 100
AttributeError: can't set attribute

为了让 mypy 开心,你基本上有两个选择:

  1. 使FooInterface.x也成为只读属性
  2. FooWithProperty.x 实现 setter 以使其可写

我猜在你的情况下,你可能想采用方法 1。如果你这样做,mypy 将正确指出行 f.x = 100 是不允许的:

from abc import abstractmethod

class FooInterface:
    # Marking this property as abstract is *optional*. If you do it,
    # mypy will complain if you forget to define x in a subclass.
    @property
    @abstractmethod
    def x(self) -> int: ...

class FooWithAttribute(FooInterface):
    # No complaints from mypy here: having this attribute be writable
    # won't violate the Liskov substitution principle -- it's safe to
    # use FooWithAttribute in any location that expects a FooInterface.
    x: int = 0

class FooWithProperty(FooInterface):
    @property
    def x(self) -> int:
        return 0

def mutate(f: FooInterface) -> None:
    # error: Property "x" defined in "FooInterface" is read-only
    f.x = 100

mutate(FooWithProperty())

不幸的是,由于 bug in mypy,方法 2 还不能很好地工作——mypy 不能正确理解如何处理使用 属性 覆盖属性。在这种情况下,解决方法是使 FooInterface.x 成为 属性 和 setter.