Статьи

Python 3 аннотации функций

Аннотации функций — это функция Python 3, которая позволяет добавлять произвольные метаданные в аргументы функции и возвращаемое значение. Они были частью оригинальной спецификации Python 3.0.

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

Аннотации функций указаны в PEP-3107 . Основной мотивацией было предоставление стандартного способа связать метаданные с аргументами функции и возвращаемым значением. Многие члены сообщества нашли новые варианты использования, но использовали разные методы, такие как пользовательские декораторы, пользовательские форматы документов и добавление пользовательских атрибутов в объект функции.

Важно понимать, что Python не благословляет аннотации какой-либо семантикой. Он просто обеспечивает приятную синтаксическую поддержку связывания метаданных, а также простой способ доступа к ним. Кроме того, аннотации не являются обязательными.

Давайте посмотрим на пример. Вот функция foo (), которая принимает три аргумента с именами a, b и c и печатает их сумму. Обратите внимание, что foo () ничего не возвращает. Первый аргумент а не аннотирован. Второй аргумент b помечается строкой «annotating b», а третий аргумент c — типом int . Возвращаемое значение аннотируется типом float . Обратите внимание на синтаксис «->» для аннотирования возвращаемого значения.

python def foo(a, b: 'annotating b', c: int) -> float: print(a + b + c)

Аннотации никак не влияют на выполнение функции. Давайте вызовем foo () дважды: один раз с аргументами типа int и один раз с аргументами строки. В обоих случаях foo () делает правильные вещи, а аннотации просто игнорируются.

« `python foo (‘Hello’, ‘,’, ‘World!’) Привет, Мир!

foo (1, 2, 3) 6 « `

Аргументы по умолчанию указываются после аннотации:

« `python def foo (x: ‘аргумент по умолчанию 5’ = 5): print (x)

foo (7) 7

foo () 5 « `

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

« `python def bar (* args, ** kwargs: ‘ключевое слово arguments dict’): print (kwargs [‘return’])

d = {‘return’: 4} бар (** d) 4 « `

Давайте вернемся к нашему первому примеру и проверим его аннотации:

« `python def foo (a, b: ‘annotating b’, c: int) -> float: print (a + b + c)

print (foo. annotations ) {‘c’: <class ‘int’>, ‘b’: ‘annotating b’, ‘return’: <class ‘float’>} « `

Это довольно просто. Если вы аннотируете функцию массивом аргументов и / или массивом аргументов ключевого слова, то, очевидно, вы не можете аннотировать отдельные аргументы.

« `python def foo (* args: ‘список неназванных аргументов’, ** kwargs: ‘dict из именованных аргументов’): print (args, kwargs)

print (foo. annotations ) {‘args’: ‘список неназванных аргументов’, ‘kwargs’: ‘dict именованных аргументов’} « `

Если вы читаете раздел о доступе к аннотациям функций в PEP-3107 , в нем говорится, что вы получаете к ним доступ через атрибут func_annotations объекта функции. Это устарело на Python 3.2. Не смущайтесь. Это просто атрибут « аннотации ».

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

python def div(a, b): """Divide a by b args: a - the dividend b - the divisor (must be different than 0) return: the result of dividing a by b """ return a / b

Может быть преобразован в:

python def div(a: 'the dividend', b: 'the divisor (must be different than 0)') -> 'the result of dividing a by b': """Divide a by b""" return a / b

Хотя та же информация фиксируется, у версии аннотаций есть несколько преимуществ:

  1. Если вы переименуете аргумент, версия документации может быть устаревшей.
  2. Проще увидеть, если аргумент не задокументирован.
  3. Нет необходимости придумывать специальный формат документации аргументов внутри строки документации для анализа инструментами. Атрибут annotations обеспечивает прямой стандартный механизм доступа.

Другое использование, о котором мы поговорим позже, — необязательная типизация. Python динамически типизирован, что означает, что вы можете передать любой объект в качестве аргумента функции. Но часто функции требуют, чтобы аргументы были определенного типа. С помощью аннотаций вы можете указать тип рядом с аргументом очень естественным образом.

Помните, что простое указание типа не приведет к его применению, и потребуется дополнительная работа (много работы). Тем не менее, даже простое указание типа может сделать намерение более читабельным, чем указание типа в строке документации, и это может помочь пользователям понять, как вызывать функцию.

Еще одно преимущество аннотаций перед строкой документации заключается в том, что вы можете прикреплять метаданные разных типов в виде кортежей или диктов. Опять же, вы можете сделать это с помощью docstring, но это будет основано на тексте и потребует специального анализа.

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

Предположим, вы хотите аннотировать аргумент как его типом, так и строкой справки. Это очень легко с аннотациями. Вы можете просто аннотировать аргумент с помощью dict, который имеет два ключа: «type» и «help».

« `python def div (a: dict (type = float, help = ‘the divid’), b: dict (type = float, help = ‘делитель (должен отличаться от 0)’)) -> dict ( type = float, help = ‘результат деления a на b’): «» «Divide a by b» »» return a / b

print (div. annotations ) {‘a’: {‘help’: ‘the divind’, ‘type’: float}, ‘b’: {‘help’: ‘делитель (должен отличаться от 0)’, ‘ type ‘: float},’ return ‘: {‘ help ‘:’ результат деления a на b ‘,’ type ‘: float}} « `

Аннотации и декораторы идут рука об руку. Для хорошего ознакомления с Python-декораторами, ознакомьтесь с двумя моими уроками: глубокое погружение в Python-декораторы и написание собственных декораторов Python .

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

Однако настоящая сила декораторов в том, что они могут действовать на аннотации. Это требует координации, конечно же, о семантике аннотаций.

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

python def check_range(f): def decorated(*args, **kwargs): for name, range in f.__annotations__.items(): min_value, max_value = range if not (min_value <= kwargs[name] <= max_value): msg = 'argument {} is out of range [{} - {}]' raise ValueError(msg.format(name, min_value, max_value)) return f(*args, **kwargs) return decorated

Теперь давайте определим нашу функцию и украсим ее декораторами @check_range .

python @check_range def foo(a: (0, 8), b: (5, 9), c: (10, 20)): return a * b - c

Давайте вызовем foo () с разными аргументами и посмотрим, что произойдет. Когда все аргументы находятся в пределах их диапазона, проблем нет.

python foo(a=4, b=6, c=15) 9

Но если мы установим значение c равным 100 (вне диапазона (10, 20)), то возникает исключение:

python foo(a=4, b=6, c=100) ValueError: argument c is out of range [10 - 20]

Есть несколько ситуаций, когда декораторы лучше, чем аннотации для прикрепления метаданных.

Один очевидный случай — если ваш код должен быть совместим с Python 2.

Другой случай, если у вас много метаданных. Как вы видели ранее, хотя можно добавлять любое количество метаданных, используя в качестве аннотаций dicts, это довольно громоздко и фактически ухудшает читабельность.

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

Аннотации — это просто атрибут dict функции.

python type(foo.__annotations__) dict

Это означает, что вы можете изменять их на лету во время работы программы. Какие есть варианты использования? Предположим, вы хотите узнать, используется ли когда-либо значение аргумента по умолчанию. Всякий раз, когда функция вызывается со значением по умолчанию, вы можете увеличивать значение аннотации. Или, может быть, вы хотите суммировать все возвращаемые значения. Динамический аспект может быть сделан внутри самой функции или декоратором.

« `python def add (a, b) -> 0: результат = a + b add. аннотации [‘return’] + = результат возвращаемый результат

распечатать (добавить аннотации [‘return’]) 0

добавить (3, 4) 7 распечатать (добавить аннотации [‘возврат’]) 7

add (5, 5) 10 print (add. annotations [‘return’]) 17 « `

Функциональные аннотации универсальны и интересны. У них есть потенциал, чтобы вступить в новую эру интроспективных инструментов, которые помогают разработчикам осваивать все более и более сложные системы. Они также предлагают более продвинутому разработчику стандартный и удобный для чтения способ непосредственного связывания метаданных с аргументами и возвращаемым значением для создания пользовательских инструментов и взаимодействия с декораторами. Но нужна определенная работа, чтобы извлечь из них пользу и использовать их потенциал.

Изучите Python с нашим полным руководством по питону, независимо от того, начинаете ли вы или начинающий программист, ищущий новые навыки.