Всем привет. Я решил полностью разобраться в пайтоновских аннотациях и заодно перевести цикл PEP-ов, документирующих эту тему. Мы начнём со стандартов версии 3.X и закончим нововведениями в python 3.8. Сразу говорю, что этот PEP — один из самых базовых и его прочтение пригодится лишь новичкам. Ну что же, поехали:


PEP 572 — Аннотации в функциях

PEP 3107
Название: Аннотации в функциях
Авторы: Collin Winter <collinwinter at google.com>, Tony Lownds <tony at lownds.com>
Статус: Окончательный
Тип: Стандарт
Создано: 2-Dec-2006
Версия Python: 3.0

Аннотация к стандарту


Данный PEP вводит синтаксис для добавления произвольных аннотаций (метаданных) к функциям в Python.

Обоснование


У функций в версии Python 2.x отсутствовал встроенный способ аннотирования параметров и возвращаемых значений. Для устранения этого пробела появилось множество инструментов и библиотек. Некоторые из них используют декораторы, описанные в PEP 318, в то время как другие анализируют docstring функции и ищут там информацию.

Этот PEP призван обеспечить единый, стандартный способ аннотирования функций, чтобы уменьшить существовавшую до этого момента путаницу, вызванную широким разбросом в механизмах и синтаксисе.

Основы аннотаций в функциях


Прежде чем приступить к обсуждению аннотаций функций Python 3.0, давайте сначала в общих чертах поговорим об их особенностях:

  1. Аннотации для параметров и возвращаемых значений функций являются полностью необязательными.
  2. Аннотации — не более чем способ связать произвольные выражения к различными частями функции во время компиляции.

    Сам по себе Python не обращает никакого внимания на аннотации. Единственное, он позволяет получить доступ к ним, что описано в разделе «Получаем доступ к аннотациям функций» ниже.

    Аннотации приобретают смысл лишь когда интерпретируются сторонними библиотеками. Они могут делать с аннотациями функций всё, что захотят. Например, одна библиотека может использовать строковые аннотации, чтобы предоставить улучшенные справочные данные, например:

    def compile(source: "something compilable",
                filename: "where the compilable thing comes from",
                mode: "is this a single statement or a suite?"):
        ...

    Другая же библиотека может использовать аннотации функций и методов Python для проверки соответствия типов. Эта библиотека может использовать аннотации для того чтобы показать, какие типы входных данных она ожидает и какой тип данных она вернёт. Возможно, это будет что-то в таком духе:

    def haul(item: Haulable, *vargs: PackAnimal) -> Distance:
        ...

    Ещё раз повторимся: аннотации ни одного из примеров сами по себе не имеют никакого значения. Настоящий смысл они приобретают только в комбинации со сторонними библиотеками.
  3. Как следует из пункта 2, этот PEP не пытается ввести какой-либо стандартный механизм обработки аннотаций даже для встроенных типов. Вся эта работа возлагается на сторонние библиотеки

Синтаксис


Параметры


Аннотации параметров представляют из себя необязательные выражения, следующие за именем самого параметра:

def foo (a: выражение, b: выражение = 5):
    ...

В псевдограмматике параметры теперь выглядят как: параметр [: выражение] [= выражение]. То есть аннотации, как и значения по умолчанию, являются необязательными и всегда предшествуют последним. В точности также, как знак равенства используется для обозначения значения по умолчанию, двоеточие используется для аннотаций. Все аннотационные выражения, как и значения по умолчанию, оцениваются при выполнении определения функции. Получение «дополнительных» аргументов (т.е. *args и **kwargs) имеет такой же синтаксис:

def foo(*args: expression, **kwargs: expression):
    ...

Аннотации для вложенных параметров всегда следуют за именем параметра, а не за последней скобкой. Аннотирование каждого «имени» во вложенном параметре не требуется:

def foo((x1, y1: expression),
        (x2: expression, y2: expression)=(None, None)):
    ...

Возвращаемые значения


До сих пор мы не привели пример того, как аннотировать тип возвращаемого значения в функциях. Это делается так:

def sum() -> expression:
    ...

То есть за списком параметров теперь может следовать литерал -> и какое-то выражение. Как и аннотации параметров, это выражение будет оцениваться при выполнении определения функции.

Полная грамматика объявления функций теперь такова:

decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
decorators: decorator+
funcdef: [decorators] 'def' NAME parameters ['->' test] ':' suite
parameters: '(' [typedargslist] ')'
typedargslist: ((tfpdef ['=' test] ',')*
                ('*' [tname] (',' tname ['=' test])* [',' '**' tname]
                 | '**' tname)
                | tfpdef ['=' test] (',' tfpdef ['=' test])* [','])
tname: NAME [':' test]
tfpdef: tname | '(' tfplist ')'
tfplist: tfpdef (',' tfpdef)* [',']

Лямбды


Lambda функции не поддерживают аннотации. Конечно, синтаксис можно было поправить, добавив возможность «оборачивать» аргументы в скобки, однако было решено не вносить такие изменение, потому что:

  1. Это нарушило бы обратную совместимость.
  2. Лямбды нейтральны и анонимны по своему определению
  3. Лямбды можно всегда переписать в виде функций


Получаем доступ к аннотациям функций


После компиляции, аннотации функции становятся доступны через атрибут __annotations__. Этот атрибут представляет собой изменяемый словарь, сопоставляющий имена переменных и значения указанных к ним аннотаций.

В словаре __annotations__ есть специальный ключ «return». Этот ключ присутствует только в том случае, если для возвращаемого значения функции была также определена ??аннотация. Например, следующая аннотация:

def foo (a: 'x', b: 5 + 6, c: list) -> max (2, 9):
    ...

Будет возвращена через атрибут __annotations__ как:

{'a': 'x',
 'b': 11,
 'c': list,
 'return': 9}

Имя ключа «return» было выбрано из-за того, что оно не может конфликтовать с именем параметра (любая попытка использовать return в качестве имени параметра приведет к исключению SyntaxError).

Атрибут __annotations__ будет пустым, если в функции нет аннотаций или же функция была создана через лямбда-выражение.

Случаи использования


В ходе обсуждения аннотаций, мы рассмотрели несколько вариантов их использования. Некоторые из них представлены здесь и сгруппированы в зависимости от «класса» информации, которую передают. Также сюда были включены примеры существующих продуктов и пакетов, которые могут использовать аннотирование.

  • Предоставление информации о типах
    • Проверка типа
    • Подсказки от IDE об ожидаемых и возвращаемых типах аргументов
    • Перегрузка функций/generic функции [прим. перегрузка функций популярна в других языках и заключается в существовании нескольких функций с одинаковыми именами, но при этом, количество принимаемых ими параметров различается]
    • «Мосты» между разными языками программирования
    • Адаптация
    • Предикатные логические функции
    • Mapping запроса к базе данных
    • Маршалинг параметров RPC [прим. Маршалинг — процесс преобразования информации, хранящейся в оперативной памяти, в формат, пригодный для хранения или передачи. RPC — удалённый вызов процедур]
  • Предоставление другой информации
    • Документирование параметров и возвращаемых значений

Стандартная библиотека


Модули pydoc и inspect


Модуль pydoc будет отображать аннотации в справочной информации о функциях. Модуль inspect изменится и будет поддерживать аннотации.

Связь с другими PEP


Объекты подписи функций


Объекты подписи функции должны предоставлять аннотации функций. Объект Parameter и другие вещи могут измениться. [прим. Подпись (Сигнатура) функции — часть её общего объявления, позволяющая средствам трансляции идентифицировать функцию среди других]

Реализация


Эталонная реализация была включена в ветку py3k (ранее «p3yk») как ревизия 53170

Отклоненные предложения


  • BDFL [прим. великодушный пожизненный диктатор] отклонил идею автора о добавлении специального синтаксиса, который позволил бы писать аннотации к генераторам, по причине: «Слишком уродливо»
  • Хоть включение специальных объектов в stdlib для аннотирования функций генераторов и функций более высокого порядка и обсуждалось на ранних этапах, в конечном итоге эта идея была отклонена, как более подходящая для сторонних библиотек. Включение такого в стандартную библиотеку вызвало бы слишком много острых вопросов.
  • Несмотря на значительные дискуссии о стандартном синтаксисе параметризации типов, было решено, что эту реализацию также следует оставить за сторонними библиотеками.
  • Несмотря на дальнейшее обсуждение, было решено не стандартизировать механизм взаимодействия аннотаций. Такое соглашение на данном этапе было бы преждевременным. Мы хотим позволить этим соглашениям развиваться органично, основываясь на реальной ситуации, а не пытаться заставить всех пользоваться какой-то надуманной схемой.