Подтвердить что ты не робот

Как мне указать, что возвращаемый тип метода такой же, как и сам класс?

У меня есть следующий код в 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) говорит, что ссылочная позиция не может быть разрешена (в методе __add__). Как мне указать, что я ожидаю, что возвращаемый тип будет иметь тип Position?

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

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

4b9b3361

Ответ 1

TL; DR: если вы используете Python 4.0, он просто работает. С сегодняшнего дня (2019) в 3. 7+ вы должны включить эту функцию, используя оператор будущего (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 по-прежнему является языком с динамической типизацией, поэтому проверка типов во время выполнения не выполняется, ввод аннотаций не должен влиять на производительность, верно? Неправильно! До Python 3.7 модуль ввода был одним из самых медленных модулей Python в ядре, поэтому, если вы 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 и это будет просто работать.

Ответ 2

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

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

Проверьте https://www.python.org/dev/peps/pep-0484/#forward-references - инструменты, соответствующие этому, будут знать, как развернуть имя класса оттуда и использовать его. (Всегда важно иметь помните, что сам язык Python ничего не делает из этих аннотаций - они обычно предназначены для анализа статического кода, или можно иметь библиотеку/среду для проверки типов во время выполнения - но вы должны явно установить это)

Ответ 3

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

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)