Как аннотировать атрибут, который может быть реализован как свойство? - PullRequest
2 голосов
/ 12 октября 2019

Я пытаюсь осчастливить mypy аннотациями к моему типу. Вот минимальный пример:

class FooInterface:
    x: int


class FooWithAttribute(FooInterface):
    x: int = 0


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

Насколько я понимаю, все в порядке: и FooWithAttribute().x, и FooWithProperty().x вернут 0, что составляет int, без ошибок типа. Однако mypy жалуется:

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

Есть ли способ сказать mypy, что все в порядке? Прямо сейчас единственный способ, который я нашел, это аннотировать x: typing.Any в FooInterface, который тратит впустую информацию о том, что x является int.

1 Ответ

2 голосов
/ 12 октября 2019

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())

Подход 2, к сожалению, пока не совсем работает из-за ошибки в mypy - mypy не правильнопонять, как обрабатывать переопределение атрибута свойством. Обходной путь в этом случае должен сделать FooInterface.x свойством с установщиком.

...