Вопрос-Ответ

How do I type hint a method with the type of the enclosing class?

Как мне ввести подсказку методу с типом заключающего класса?

У меня есть следующий код на Python 3:

class Position:

def __init__(self, x: int, y: int):
self.x = x
self.y = y

def __add__(self, other: Position) -> Position:
return Position(self.x + other.x, self.y + other.y)

Но мой редактор (PyCharm) говорит, что ссылка Position не может быть разрешена (в __add__ методе). Как я должен указать, что я ожидаю, что возвращаемый тип будет иметь тип Position?

Я думаю, что на самом деле это проблема PyCharm. На самом деле он использует информацию в своих предупреждениях и завершении кода.

Но поправьте меня, если я ошибаюсь, и нужно использовать какой-то другой синтаксис.

Переведено автоматически
Ответ 1

TL; DR: На сегодняшний день (2019) в Python 3.7+ вы можете включить эту функцию, используя оператор "future", from __future__ import annotations.

(Поведение, включенное в from __future__ import annotations может стать стандартным в будущих версиях Python, и собиралось стать стандартным в Python 3.10. Однако изменение в 3.10 было отменено в последнюю минуту, и теперь может вообще не произойти. Python 3.11 не включает его)

В Python 3.6 или ниже следует использовать строку.


Я предполагаю, что вы получили это исключение:

NameError: name 'Position' is not defined

Это потому, что Position должно быть определено, прежде чем вы сможете использовать его в аннотации, если только вы не используете Python с включенными изменениями PEP 563.

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 3.10, но теперь это изменение отложено. Поскольку Python по-прежнему является языком с динамической типизацией, поэтому во время выполнения проверка типов не выполняется, ввод аннотаций не должен влиять на производительность, верно? Неправильно! До Python 3.7 модуль ввода был одним из самых медленных модулей python в core, поэтому для кода, который включает импорт 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 3.10 аннотации функций и переменных больше не будут оцениваться во время определения. Вместо этого строковая форма будет сохранена в соответствующем __annotations__ словаре. Средства проверки статических типов не увидят разницы в поведении, тогда как инструментам, использующим аннотации во время выполнения, придется выполнять отложенную оценку.


...


Включение будущего поведения в Python 3.7


Функциональность, описанная выше, может быть включена, начиная с Python 3.7, используя следующий специальный импорт:


from __future__ import annotations

Вещи, которые у вас может возникнуть соблазн сделать вместо этого

A. Определите фиктивный 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

B. Monkey-исправление для добавления аннотаций:

Возможно, вы захотите попробовать некоторую магию метапрограммирования 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

Вероятно, слишком много проблем.

Ответ 2

PEP 673, который реализован в Python 3.11, добавляет Self тип.

from typing import Self    

class Position:

def __init__(self, x: int, y: int):
self.x = x
self.y = y

def __add__(self, other: Self) -> Self:
return type(self)(self.x + other.x, self.y + other.y)

Возврат Self часто является хорошей идеей, но вы должны возвращать объект того же типа, что и self, что означает вызов type(self), а не Position.


Для более старых версий Python (в настоящее время 3.7 и более поздних) используйте typing-extensions пакет . Одно из его назначений -


Включить использование новых функций системы типов в старых версиях Python. Например, typing.TypeGuard является новым в Python 3.10, но typing_extensions позволяет пользователям предыдущих версий Python использовать его тоже.


Затем вы просто импортируете из typing_extensions вместо typing, например from typing_extensions import Self.

Ответ 3

Начиная с Python 3.11 (выпущен в конце 2022 года), доступен typing.Self разработанный для этой цели. Проверьте PEP 673!

Для предыдущих версий Python приходилось учитывать, что имя 'Position' недоступно во время анализа самого тела класса. Я не знаю, как вы используете объявления типов, но PEP 484 в Python - это то, что должно использовать большинство режимов, если использование этих подсказок по вводу говорит, что на этом этапе вы можете просто поместить имя в виде строки:

def __add__(self, other: 'Position') -> 'Position':
return Position(self.x + other.x, self.y + other.y)

Проверьте PEP 484 раздел о прямых ссылках - инструменты, соответствующие этому, будут знать, как развернуть имя класса оттуда и использовать его. (Всегда важно иметь в виду, что сам язык Python ничего не делает с этими аннотациями. Обычно они предназначены для статического анализа кода, или у вас может быть библиотека / фреймворк для проверки типов во время выполнения - но вы должны явно установить это.)

Обновление: Также, начиная с Python 3.7, ознакомьтесь с PEP 563. Начиная с Python 3.8, можно написать from __future__ import annotations для отсрочки вычисления аннотаций. Классы с прямой ссылкой должны работать просто.

Обновление 2: начиная с Python 3.10, PEP 563 пересматривается, и может быть, что вместо этого используется PEP 649 - это просто позволило бы использовать имя класса, простое, без каких-либо кавычек: предложение pep заключается в том, что оно разрешается ленивым способом.

Обновление 3: Начиная с Python 3.11, упомянутые выше PEPs 563 и 649 для разрешения прямых ссылок все еще конкурируют, и, вероятно, ни один из них не будет продвигаться вперед, как сейчас.

Ответ 4

Указание типа в виде string - это нормально, но меня всегда немного раздражает, что мы в основном обходим анализатор. Так что вам лучше не вводить с ошибками ни одну из этих буквенных строк:

def __add__(self, other: 'Position') -> 'Position':
return Position(self.x + other.x, self.y + other.y)

Небольшое изменение заключается в использовании связанного typevar , по крайней мере, тогда вам придется вводить строку только один раз при объявлении typevar:

from typing import TypeVar

T = TypeVar('T', bound='Position')

class Position:

def __init__(self, x: int, y: int):
self.x = x
self.y = y

def __add__(self, other: T) -> T:
return Position(self.x + other.x, self.y + other.y)
python python-3.x