Оценивает ли Python подсказку типа прямой ссылки? - PullRequest
0 голосов
/ 24 марта 2019

Я просматривал раздел PEP 484 на Пересылка ссылок и заметил утверждение:

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

И это заставило меня задуматься, когда «позже» и чем? Интерпретатор не пытается позже распознать его как литерал, так что же делать? Это просто, если сторонний инструмент написан для этого?

Небольшой пример для демонстрации результата интерпретатора:

class A:
    def test(self, a: 'A') -> None:
        pass
class B:
    def test(self, a: A) -> None:
        pass

>>> A().test.__annotations__
{'a': 'A', 'return': None}
>>> B().test.__annotations__
{'a': <class '__main__.A'>, 'return': None}

Если мое понимание аннотаций функций и подсказок типов правильное, Python на самом деле не делает что-либо с ними во время выполнения, чтобы улучшить производительность, а скорее интроспективное использование позволяет строго треть сторонние приложения, такие как линтеры, IDE и инструменты статического анализа (например, mypy), чтобы воспользоваться их доступностью. Итак, будут ли эти инструменты пытаться разрешить подсказку типа 'A' вместо того, чтобы выполнять эту работу для данного переводчика, и если да, то как они этого добьются?

Обновление:

Используя модуль typing, код пользователя может выполнять следующие действия:

>>> typing.get_type_hints(A().test)
{'a': <class '__main__.A'>, 'return': <class 'NoneType'>}
>>> typing.get_type_hints(B().test)
{'a': <class '__main__.A'>, 'return': <class 'NoneType'>}

Однако мой вопрос направлен на то, несет ли Python ответственность за обновление __annotations__ функции из строкового литерала, то есть при изменении времени выполнения:

>>> A().test.__annotations__
{'a': 'A', 'return': None}

до ...

>>> A().test.__annotations__
{'a': <class '__main__.A'>, 'return': None}

Если Python этого не делает, то зачем мне строковый литерал в качестве подсказки типа, отличной от самодокументированного кода? Какое значение придает первая форма мне, пользователю или стороннему инструменту?

1 Ответ

2 голосов
/ 25 марта 2019

Рассмотрим следующий код:

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 проверках и т. П.)

...