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

时间:2019-10-11 23:10:01

标签: 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

据我所知,一切都很好:FooWithAttribute().xFooWithProperty().x都将返回0,即int,没有类型错误。但是mypy抱怨:

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

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

1 个答案:

答案 0 :(得分:2)

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的设置工具使其可写

我猜在您的情况下,您可能希望采用方法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的属性。