Почему mypy сообщает о несовместимых типах, когда дочерний и родительский класс удовлетворяют определению типа «прародитель»? - PullRequest
1 голос
/ 14 октября 2019

С учетом следующего кода:

from typing import Tuple


class Grandparent:
    items: Tuple[str, ...] = ()


class Parent(Grandparent):
    items = ('foo',)


class Child(Parent):
    items = ('foo', 'bar')

mypy сообщает о следующей ошибке:

error: Incompatible types in assignment (expression has type "Tuple[str, str]", base class "Parent" defined the type as "Tuple[str]")

При таком изменении кода (снова указав тот же тип в Parentclass) удовлетворяет mypy:

from typing import Tuple


class Grandparent:
    items: Tuple[str, ...] = ()


class Parent(Grandparent):
    items: Tuple[str, ...] = ('foo',)


class Child(Parent):
    items = ('foo', 'bar')

Почему мне нужно повторно указывать один и тот же тип для items в нескольких местах в иерархии классов, учитывая, что присвоение items во всех местах удовлетворяеттакое же / оригинальное определение? И есть ли способ избежать необходимости делать это?

1 Ответ

1 голос
/ 14 октября 2019

Я считаю, что это выбор дизайна, который сделал mypy. Короче говоря, суть вашего вопроса такова: когда мы переопределяем какой-либо атрибут, хотим ли мы использовать тот же тип, что и родительский, или использовать новый переопределенный тип?

Mypy выбрал первый - этовозможно, более интуитивно понятный во многих случаях. Например, если у меня есть следующая иерархия классов:

class Parent:
    def foo(self, p1: int) -> None: ...

class Child(Parent):
    def foo(self, p1: int, p2: str = "bar") -> None: ...

... имеет смысл для Child.foo иметь тип def (self: Child, p1: int, p2: str = ...) -> None вместо прямого наследования типа Parent.foo, чтоis def (self: Parent, p1 : int) -> None.

Таким образом, все еще проверяет тип, если вы делаете Child().foo(1, "a"). В более широком смысле, полезно разрешить уточнять родительский тип, с единственным ограничением в том, что дочернему элементу все еще необходимо следовать принципу подстановки Лискова после уточнения.

И если правилоявляется то, что дочернее определение выигрывает для методов, тогда имеет смысл применять то же правило к атрибутам для согласованности.

А что касается того, как обойти эту проблему - на вашем месте, я бывозможно, либо просто согласитесь продолжить добавление аннотации типа к каждому назначению. Я не думаю, что это является большим бременем.

В качестве альтернативы, я мог бы рассмотреть просто сведение всей иерархии классов в один класс, который принимает соответствующий кортеж в качестве параметра в __init__, чтобы попытаться отбросить его. Отступите от необходимости что-то жестко кодировать для начала. Но это может быть нереальным решением для всего, что вы пытаетесь сделать.

...