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

san*_*ash 5 python properties typing mypy

我试图让 mypy 对我的类型注释感到满意。这是最小的例子:

class FooInterface:
    x: int


class FooWithAttribute(FooInterface):
    x: int = 0


class FooWithProperty(FooInterface):
    @property
    def x(self) -> int:
        return 0
Run Code Online (Sandbox Code Playgroud)

为了我的人的理解,一切都很好:既有FooWithAttribute().xFooWithProperty().x将返回0int,没有类型的错误。然而 mypy 抱怨:

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

有没有办法告诉mypy一切正常?现在我发现唯一的办法就是标注x: typing.AnyFooInterface其废物x是INT的信息。

Mic*_*x2a 7

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

def mutate(f: FooInterface) -> None:
    f.x = 100
Run Code Online (Sandbox Code Playgroud)

看起来不错,对吧?但是如果我们这样做会发生什么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
Run Code Online (Sandbox Code Playgroud)

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

  1. 使FooInterface.x也是只读属性
  2. 实现一个 setterFooWithProperty.x以使其可写

我猜在你的情况下,你可能想要采用方法 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())
Run Code Online (Sandbox Code Playgroud)

不幸的是,由于mypy 中的一个错误,方法 2 还不能完全工作——mypy 不能正确理解如何使用属性覆盖属性。在这种情况下的解决方法是FooInterface.x使用 setter创建一个属性。