TL; DR : если вы используете Python 4.0, он просто работает. С сегодняшнего дня (2019) в 3.7+ вы должны включить эту функцию, используя оператор future ( from __future__ import annotations
) - для Python 3.6 или ниже используйте строку.
Я думаю, вы получили это исключение:
NameError: name 'Position' is not defined
Это потому, что Position
необходимо определить, прежде чем вы сможете использовать его в аннотации, если вы не используете Python 4.
Python 3.7+: from __future__ import annotations
Python 3.7 представляет PEP 563: отложенная оценка аннотаций . Модуль, который использует оператор future, from __future__ import annotations
будет автоматически сохранять аннотации в виде строк:
from __future__ import annotations
class Position:
def __add__(self, other: Position) -> Position:
...
Это запланировано, чтобы стать по умолчанию в Python 4.0. Поскольку Python по-прежнему является языком с динамической типизацией, поэтому проверка типов во время выполнения не выполняется, ввод аннотаций не должен влиять на производительность, верно? Неправильно! Перед тем как питон 3.7, модуль типирование используется как один из самых медленных питона модулей в ядре , так если вы import typing
увидите до 7 - кратного увеличения производительности при обновлении до 3.7.
Python <3.7: использовать строку
Согласно PEP 484 , вы должны использовать строку вместо самого класса:
class Position:
...
def __add__(self, other: 'Position') -> 'Position':
...
Если вы используете инфраструктуру Django, это может быть знакомо, поскольку модели Django также используют строки для прямых ссылок (определения внешнего ключа, где внешняя модель еще объявлена self
или еще не объявлена). Это должно работать с Pycharm и другими инструментами.
источники
Соответствующие части PEP 484 и PEP 563 , чтобы избавить вас от поездки:
Прямые ссылки
Когда подсказка типа содержит имена, которые еще не определены, это определение может быть выражено как строковый литерал, который будет решен позже.
Ситуация, в которой это обычно происходит, - это определение класса контейнера, где определяемый класс встречается в сигнатуре некоторых методов. Например, следующий код (начало реализации простого двоичного дерева) не работает:
class Tree:
def __init__(self, left: Tree, right: Tree):
self.left = left
self.right = right
Для решения этой проблемы мы пишем:
class Tree:
def __init__(self, left: 'Tree', right: 'Tree'):
self.left = left
self.right = right
Строковый литерал должен содержать допустимое выражение Python (т. Е. Compile (lit, '', 'eval') должен быть допустимым объектом кода), и он должен вычисляться без ошибок после полной загрузки модуля. Локальное и глобальное пространство имен, в котором оно оценивается, должно быть теми же пространствами имен, в которых будут оцениваться аргументы по умолчанию для одной и той же функции.
и PEP 563:
В Python 4.0 аннотации функций и переменных больше не будут оцениваться во время определения. Вместо этого строковая форма будет сохранена в соответствующем __annotations__
словаре. Проверщики статического типа не увидят никакой разницы в поведении, тогда как инструменты, использующие аннотации во время выполнения, должны будут отложить оценку.
...
Описанную выше функциональность можно включить начиная с Python 3.7, используя следующий специальный импорт:
from __future__ import annotations
Вещи, которые вы можете испытать вместо этого
А. Определить манекен Position
Перед определением класса поместите фиктивное определение:
class Position(object):
pass
class Position(object):
...
Это избавит от NameError
и может даже выглядеть хорошо:
>>> Position.__add__.__annotations__
{'other': __main__.Position, 'return': __main__.Position}
Но так ли это?
>>> for k, v in Position.__add__.__annotations__.items():
... print(k, 'is Position:', v is Position)
return is Position: False
other is Position: False
Б. Обезьяна-патч для добавления аннотаций:
Возможно, вы захотите попробовать магию метапрограммирования на Python и написать декоратор, который исправит определение класса, чтобы добавить аннотации:
class Position:
...
def __add__(self, other):
return self.__class__(self.x + other.x, self.y + other.y)
Декоратор должен нести ответственность за эквивалент этого:
Position.__add__.__annotations__['return'] = Position
Position.__add__.__annotations__['other'] = Position
По крайней мере, это кажется правильным:
>>> for k, v in Position.__add__.__annotations__.items():
... print(k, 'is Position:', v is Position)
return is Position: True
other is Position: True
Вероятно, слишком много проблем.
Вывод
Если вы используете 3.6 или ниже, используйте строковый литерал, содержащий имя класса, в 3.7 используйте, from __future__ import annotations
и это будет просто работать.
typing
поскольку любой тип, который вы используете, должен находиться в области видимости при оценке строки.''
from __future__ import annotations
- это должен быть импортирован до всех других импортов.Указывать тип в виде строки хорошо, но всегда немного радует, что мы в основном обходим синтаксический анализатор. Так что лучше не писать ни одной из этих буквенных строк:
Небольшое изменение заключается в использовании связанного typevar, по крайней мере, тогда вы должны написать строку только один раз при объявлении typevar:
источник
typing.Self
указывал это явно.typing.Self
существовало ли что-то подобное вашему . Возвращение жестко закодированной строки не может вернуть правильный тип при использовании полиморфизма. В моем случае я хотел реализовать метод десериализации класса. Я решил вернуть диктет (kwargs) и позвонитьsome_class(**some_class.deserialize(raw_data))
.Position
, а не класс, поэтому приведенный выше пример технически некорректен. Реализация должна заменитьPosition(
что-то вродеself.__class__(
.other
, но, скорее всего, это зависит отself
. Таким образом, вам нужно поместить аннотациюself
для описания правильного поведения (и, возможно,other
простоPosition
показать, что она не связана с типом возвращаемого значения). Это также может быть использовано в тех случаях, когда вы работаете только сself
. напримерdef __aenter__(self: T) -> T:
Имя «Позиция» недоступно во время анализа самого тела класса. Я не знаю, как вы используете объявления типов, но PEP 484 в Python - это то, что должен использовать большинство режимов, если с помощью этих подсказок о наборе текста говорят, что вы можете просто поместить имя в виде строки на этом этапе:
Проверьте https://www.python.org/dev/peps/pep-0484/#forward-references - инструменты, соответствующие этому, будут знать, как развернуть имя класса оттуда и использовать его. (Всегда важно иметь помните, что сам язык Python ничего не делает из этих аннотаций - они обычно предназначены для анализа статического кода, или можно иметь библиотеку / среду для проверки типов во время выполнения - но вы должны явно установить это).
Обновление Кроме того, начиная с Python 3.8, проверьте pep-563 - начиная с Python 3.8 можно писать,
from __future__ import annotations
чтобы отложить оценку аннотаций - классы прямой ссылки должны работать просто.источник
Когда подсказка типа на основе строки является приемлемой,
__qualname__
элемент также может быть использован. Он содержит имя класса и доступен в теле определения класса.Делая это, переименование класса не подразумевает изменение подсказок типа. Но я лично не ожидал бы, что умные редакторы кода будут хорошо обрабатывать эту форму.
источник