Я считаю, что это выбор дизайна, который сделал 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__
, чтобы попытаться отбросить его. Отступите от необходимости что-то жестко кодировать для начала. Но это может быть нереальным решением для всего, что вы пытаетесь сделать.