mypy: как определить возвращаемое значение как список экземпляров подкласса - PullRequest
2 голосов
/ 16 февраля 2020

Я пытаюсь определить возвращаемое значение метода foo как список AbstractChild экземпляров подкласса, но mypy продолжает выдавать ошибку.

class AbstractParent(ABC):
    @abstractmethod
    def foo(self) -> List["AbstractChild"]: pass


class AbstractChild(ABC):
    pass


class Parent(AbstractParent):
    def foo(self) -> List["Child"]: pass
#   ^ mypy: error Return type "List[Child]" of "foo" incompatible with return type "List[AbstractChild]" in supertype "AbstractParent"


class Child(AbstractChild):
    pass

Изменение типа возвращаемого значения из списка к одному значению mypy перестанет жаловаться, что я нахожу довольно странным, но я все еще привыкаю к ​​системе python type, так что я могу что-то упустить.

1 Ответ

1 голос
/ 16 февраля 2020

mypy здесь правильно, потому что ваш Parent не реализует AbstractParent правильно - для этого он должен определить метод foo, который возвращает список AbstractChild ren, а не Child ren , Это связано с тем, что коллекции не являются полиморфными c (и это верно и для других языков, например, Java): List[AbstractChild] не совпадает с типом List[Child], а List[Child] не наследуется от List[AbstractChild] просто потому что Child делает. Если бы у нас не было этого ограничения, возможны ошибки, подобные этой:

class AbstractChild(ABC):
    pass

class Child(AbstractChild):
    pass

class GrandChild(AbstractChild):
    pass

grandchildren: List[GrandChild] = [GrandChild()]
all_children: List[AbstractChild] = grandchildren
all_children.append(Child())
grandchild: GrandChild = grandchildren[0]  # would pass typechecks but is a Child, actually

(это перефразированный пример ответа Джона Скита на аналогичный вопрос в Java) .

Java, например, перехватывает этот тип ошибок при компиляции и требует явной ковариации, например, List<? extends Child> для чтения и List<? super Child> для записи в список.

In В вашем случае вы бы также указали тип c. В приведенном ниже примере я изменяю AbstractParent, чтобы возвращать List элементов того же типа C, которые могут быть любыми подклассами AbstractChild, а Parent является конкретной реализацией обобщенного c AbstractChild с конкретным дочерним типом Child:

from typing import List, TypeVar, Generic


C = TypeVar('C', bound='AbstractChild')


class AbstractParent(ABC, Generic[C]):
    @abstractmethod
    def foo(self) -> List[C]: pass


class Parent(AbstractParent["Child"]):
    def foo(self) -> List["Child"]:
        return []

Дополнительные примеры можно найти в главе Generics из mypy документов, в частности, Variance of generi c типы секция.

...