Псевдонимы типов и генерики в Python
Как исправить загадочную ошибку Python Mypy: expects no type arguments, but 1 given [type-arg]
Приходилось ли вам слышать про псевдонимы типов в Питоне?
На первый взгляд, теория выглядит сложной и абстрактной. Давайте разберемся на элементарном примере.
Представьте, что у вас список животных.
И у вас есть функция escaped()
применимая к списку любых животных.
Но по каким-то причинам вам нужны специализированные списки - список птиц, список рептилий и т.д.
И вам хочется использовать проверки типов, чтобы нельзя было добавить птицу в список рептилий.
Я в курсе про летающих динозавров, но давайте без педантизма, тем более что летающие динозавры - не птицы.
Пишем простейших код.. и ловим совсем невнятную ошибку:
from typing import List
class Animal:
escaped: bool
class Bird(Animal):
pass
class AnimalList(List[Animal]):
def escaped(self):
return [mammal for mammal in self if mammal.escaped]
a = AnimalList()
a.append(Bird()) # ok
class Zoo:
aviary: AnimalList[Bird] # Mypy: "AnimalList" expects no type arguments, but 1 given [type-arg]
Что это за [type-arg]
и почему нет проблем с List[Bird]
но нельзя AnimalList[Bird]
?
List[Animal]
создает список для конкретного класса, в отличие от исходного генерика list
.
И уже просто негде указывать тип элементов для AnimalList
- это уже не генерик.
Решение элементарно - надо создать alias для типа Animal
.
Тогда в AnimalList
останется возможность указать тип элементов.
from typing import List, TypeVar
class Animal:
escaped: bool
AnimalSubClass = TypeVar("AnimalSubClass", bound=Animal)
class Bird(Animal):
pass
class Reptile(Animal):
pass
class AnimalList(List[AnimalSubClass]):
def escaped(self):
return [mammal for mammal in self if mammal.escaped]
class Zoo:
aviary: AnimalList[Bird]
aviary = Zoo()
aviary.aviary.append(Bird()) # ok
aviary.aviary.append(Reptile()) # Mypy: Argument 1 to "append" of "list" has incompatible type "Reptile"; expected "Bird" [arg-type]
Если вам это кажется абстрактным вопросом без практического применения, посмотрите на мой код использующий type alias точно для такой ситуации.
Если вам интересно, что за аргумент bound
в TypeVar
в коде выше, то это просто заставляет определяемый type alias принимать как Animal
так и любого наследника Animal
.