Рассмотрим следующий код:
class Foo:
def bar(self) -> Foo:
return Foo()
Эта программа действительно завершится сбоем во время выполнения, если вы попытаетесь запустить ее с Python: когда интерпретатор увидит определение bar
, определение Foo
еще не закончено. Таким образом, поскольку Foo
еще не добавлено в глобальное пространство имен, мы пока не можем использовать его как подсказку типа.
Аналогично рассмотрим эту программу:
class Foo:
def bar(self) -> Bar:
return Bar()
class Bar:
def foo(self) -> Foo:
return Foo()
Это взаимозависимое определение страдает той же проблемой: пока мы оцениваем Foo
, Bar
еще не была оценена, поэтому интерпретатор выдает исключение.
Есть три решения этой проблемы. Первый заключается в том, чтобы сделать некоторые из ваших строк подсказок типа, эффективно «объявляя их вперед»:
class Foo:
def bar(self) -> "Foo":
return Foo()
Это удовлетворяет интерпретатору Python и не нарушает работу сторонних инструментов, таких как mypy: они могут просто удалять кавычки перед анализом типа. Основным недостатком является то, что этот синтаксис выглядит некрасиво и неуклюже.
Вторым решением является использование синтаксиса комментариев типа:
class Foo:
def bar(self):
# type: () -> Foo
return Foo()
Это имеет те же преимущества и недостатки, что и первое решение: оно удовлетворяет интерпретатора и инструментария, но выглядит неприлично и безобразно. Он также имеет дополнительное преимущество, заключающееся в том, что он поддерживает ваш код обратно совместимым с Python 2.7.
Третье решение - только Python 3.7+ - используйте директиву from __future__ import annotations
:
from __future__ import annotations
class Foo:
def bar(self) -> Foo:
return Foo()
Это автоматически сделает все аннотации представленными в виде строк. Таким образом, мы получаем преимущество первого решения, но без уродства.
Это поведение в конечном итоге станет значением по умолчанию в будущих версиях Python.
Оказывается также, что автоматическое создание всех строк аннотаций может привести к некоторым улучшениям производительности. Создание типов, таких как List[Dict[str, int]]
, может быть удивительно дорогим: они просто регулярные выражения во время выполнения и оцениваются так, как если бы они были записаны как List.__getitem__(Dict.__getitem__((str, int))
.
Оценка этого выражения несколько затратна: в итоге мы выполняем два вызова метода, создаем кортеж и создаем два объекта. Конечно, это не считая дополнительной работы, которая происходит в самих __getitem__
методах - и работа, которая происходит в этих методах, оказывается нетривиальной из-за необходимости.
(Короче говоря, им нужно создавать специальные объекты, которые гарантируют, что такие типы, как List[int]
, не могут быть использованы неподходящим образом во время выполнения - например, в isinstance
проверках и т. П.)