https://habr.com/ru/post/500134/- Python
- Программирование
- Профессиональная литература
- Изучение языков
Всем привет. Я решил полностью разобраться в пайтоновских аннотациях и заодно перевести цикл PEP-ов, документирующих эту тему. Мы начнём со стандартов версии 3.X и закончим нововведениями в python 3.8. Сразу говорю, что этот PEP — один из самых базовых и его прочтение пригодится лишь новичкам. Ну что же, поехали:
PEP 572 — Аннотации в функциях
Аннотация к стандарту
Данный PEP вводит синтаксис для добавления произвольных аннотаций (метаданных) к функциям в Python.
Обоснование
У функций в версии Python 2.x отсутствовал встроенный способ аннотирования параметров и возвращаемых значений. Для устранения этого пробела появилось множество инструментов и библиотек. Некоторые из них используют декораторы, описанные в PEP 318, в то время как другие анализируют docstring функции и ищут там информацию.
Этот PEP призван обеспечить единый, стандартный способ аннотирования функций, чтобы уменьшить существовавшую до этого момента путаницу, вызванную широким разбросом в механизмах и синтаксисе.
Основы аннотаций в функциях
Прежде чем приступить к обсуждению аннотаций функций Python 3.0, давайте сначала в общих чертах поговорим об их особенностях:
- Аннотации для параметров и возвращаемых значений функций являются полностью необязательными.
- Аннотации — не более чем способ связать произвольные выражения к различными частями функции во время компиляции.
Сам по себе 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:
...
Ещё раз повторимся: аннотации ни одного из примеров сами по себе не имеют никакого значения. Настоящий смысл они приобретают только в комбинации со сторонними библиотеками.
- Как следует из пункта 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 функции не поддерживают аннотации. Конечно, синтаксис можно было поправить, добавив возможность «оборачивать» аргументы в скобки, однако было решено не вносить такие изменение, потому что:
- Это нарушило бы обратную совместимость.
- Лямбды нейтральны и анонимны по своему определению
- Лямбды можно всегда переписать в виде функций
Получаем доступ к аннотациям функций
После компиляции, аннотации функции становятся доступны через атрибут __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 для аннотирования функций генераторов и функций более высокого порядка и обсуждалось на ранних этапах, в конечном итоге эта идея была отклонена, как более подходящая для сторонних библиотек. Включение такого в стандартную библиотеку вызвало бы слишком много острых вопросов.
- Несмотря на значительные дискуссии о стандартном синтаксисе параметризации типов, было решено, что эту реализацию также следует оставить за сторонними библиотеками.
- Несмотря на дальнейшее обсуждение, было решено не стандартизировать механизм взаимодействия аннотаций. Такое соглашение на данном этапе было бы преждевременным. Мы хотим позволить этим соглашениям развиваться органично, основываясь на реальной ситуации, а не пытаться заставить всех пользоваться какой-то надуманной схемой.