Как хорошо использовать «аннотации функций» в Python3?

159

Функциональные аннотации: PEP-3107

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

Как это устроено:

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

Все, что следует после двоеточия после аргумента, является «аннотацией», а информация, следующая за ->ним, является аннотацией для возвращаемого значения функции.

foo.func_annotations возвращает словарь:

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

Каково значение наличия этого?

agscala
источник
6
@SilentGhost: к сожалению, многие ссылки с фактическими вариантами использования не работают. Есть ли место, где контент мог бы быть сохранен, или он исчез навсегда?
максимум
16
не должно foo.func_annotations быть foo.__annotations__в python3?
zhangxaochen
2
Аннотации не имеют особого значения. Единственное, что делает Python - это помещает их в словарь аннотаций . Любое другое действие зависит от вас.
Н Рандхава
что def foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9):значит?
Али Шх

Ответы:

90

Я думаю, что это действительно здорово.

Исходя из академического опыта, я могу вам сказать, что аннотации оказались неоценимыми для создания интеллектуальных статических анализаторов для таких языков, как Java. Например, вы можете определить семантику, такую ​​как ограничения состояния, потоки, доступ к которым разрешен, ограничения архитектуры и т. Д., И существует довольно много инструментов, которые могут затем прочитать их и обработать, чтобы получить гарантии, выходящие за пределы того, что вы получаете от компиляторов. Вы могли бы даже написать вещи, которые проверяют предусловия / постусловия.

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

Существуют и другие варианты использования аннотаций, которые не поддаются проверке. Я вижу, как я могу применить свои инструменты на основе Java к Python. Например, у меня есть инструмент, который позволяет назначать специальные предупреждения методам и дает вам указание, когда вы вызываете их, что вам следует прочитать их документацию (например, представьте, что у вас есть метод, который нельзя вызывать с отрицательным значением, но он не интуитивно понятно из названия). С аннотациями я мог бы написать что-то подобное для Python. Аналогично, инструмент, который организует методы в большом классе на основе тегов, может быть написан при наличии официального синтаксиса.

Uri
источник
34
ISTM - это теоретические преимущества, которые могут быть реализованы только в том случае, если все стандартные библиотеки и сторонние модули используют аннотации функций и используют их с единообразным значением и используют продуманную систему аннотаций. До того дня (который никогда не наступит) основным использованием аннотаций функций Python будет одноразовое использование, описанное в других ответах. В настоящее время вы можете забыть об интеллектуальных статических анализаторах, заверениях компиляторов, цепочках инструментов на основе Java и т. Д.
Раймонд Хеттингер,
4
Даже без всего того, чтобы использовать аннотации функций, вы все равно можете использовать их для статического анализа в коде, который имеет их на своих входах и вызывает другой код, который аналогичным образом аннотируется. В рамках более крупного проекта или кодовой базы это все еще может быть весьма полезным телом кода для выполнения статического анализа на основе аннотаций.
GPS
1
AFAICT, вы можете делать все это с помощью декораторов, которые предшествуют аннотациям; поэтому я до сих пор не вижу выгоды. У меня немного другой взгляд на этот вопрос: stackoverflow.com/questions/13784713/…
allyourcode
9
Вернемся к 2015 году, python.org/dev/peps/pep-0484 и mypy-lang.org начинают доказывать, что все скептики неправы.
Маурисио Шеффер
1
Это также показывает влияние Python на Swift еще больше.
Учуугака
92

Аннотации функций - это то, что вы делаете из них.

Они могут быть использованы для документации:

def kinetic_energy(mass: 'in kilograms', velocity: 'in meters per second'):
     ...

Их можно использовать для проверки предварительных условий:

def validate(func, locals):
    for var, test in func.__annotations__.items():
        value = locals[var]
        msg = 'Var: {0}\tValue: {1}\tTest: {2.__name__}'.format(var, value, test)
        assert test(value), msg


def is_int(x):
    return isinstance(x, int)

def between(lo, hi):
    def _between(x):
            return lo <= x <= hi
    return _between

def f(x: between(3, 10), y: is_int):
    validate(f, locals())
    print(x, y)


>>> f(0, 31.1)
Traceback (most recent call last):
   ... 
AssertionError: Var: y  Value: 31.1 Test: is_int

Также см. Http://www.python.org/dev/peps/pep-0362/, чтобы узнать , как реализовать проверку типов.

Раймонд Хеттингер
источник
18
Как это лучше, чем строка документации для документации или явная проверка типа в функции? Это, кажется, усложняет язык без причины.
эндолит
10
@endolith Конечно, мы можем обойтись без аннотаций функций. Они просто предоставляют стандартный способ доступа к аннотациям. Это делает их доступными для help () и подсказок и делает их доступными для самоанализа.
Рэймонд Хеттингер
4
Вместо того, чтобы передавать числа, вы можете создавать типы Massи Velocityвместо этого.
правильном направлении
1
чтобы полностью продемонстрировать это, я должен был def kinetic_energy(mass: 'in kilograms', velocity: 'in meters per second') -> float:бы также показать тип возвращаемого значения. Это мой любимый ответ здесь.
Томми
Используя ваш код, есть ли способ проверить returnаннотацию? Это, кажется, не появляется вlocals
user189728
46

Это слишком поздний ответ, но AFAICT, лучшее текущее использование аннотаций функций - это PEP-0484 и MyPy .

Mypy - это необязательный статический тип проверки для Python. Вы можете добавить подсказки типов в свои программы Python, используя предстоящий стандарт для аннотаций типов, представленный в Python 3.5 beta 1 (PEP 484), и использовать mypy для статической проверки типов.

Используется так:

from typing import Iterator

def fib(n: int) -> Iterator[int]:
    a, b = 0, 1
    while a < n:
        yield a
        a, b = b, a + b
Дастин Уайетт
источник
2
Больше примеров здесь Mypy Примеры и здесь Как вы можете
El Ruso
Также смотрите pytype - другой статический анализатор, созданный с учетом PEP-0484.
GPS
К сожалению, тип не применяется. Если я печатаю list(fib('a'))с вашей примерной функцией, Python 3.7 с радостью принимает аргумент и жалуется, что нет способа сравнить строку и int.
Дени де Бернарди
@DenisdeBernardy Как объясняет PEP-484, Python предоставляет только аннотации типов. Для принудительного использования типов вы должны использовать mypy.
Дастин Уайатт
23

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

# This is in the 'mm' module

registry = {}
import inspect

class MultiMethod(object):
    def __init__(self, name):
        self.name = name
        self.typemap = {}
    def __call__(self, *args):
        types = tuple(arg.__class__ for arg in args) # a generator expression!
        function = self.typemap.get(types)
        if function is None:
            raise TypeError("no match")
        return function(*args)
    def register(self, types, function):
        if types in self.typemap:
            raise TypeError("duplicate registration")
        self.typemap[types] = function

def multimethod(function):
    name = function.__name__
    mm = registry.get(name)
    if mm is None:
        mm = registry[name] = MultiMethod(name)
    spec = inspect.getfullargspec(function)
    types = tuple(spec.annotations[x] for x in spec.args)
    mm.register(types, function)
    return mm

и пример использования:

from mm import multimethod

@multimethod
def foo(a: int):
    return "an int"

@multimethod
def foo(a: int, b: str):
    return "an int and a string"

if __name__ == '__main__':
    print("foo(1,'a') = {}".format(foo(1,'a')))
    print("foo(7) = {}".format(foo(7)))

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

Примечание . В Python вы можете получить доступ к аннотациям, function.__annotations__а не function.func_annotationsк тому func_*стилю, который был удален в Python 3.

Мухаммед Алкарури
источник
2
Интересное приложение, хотя, боюсь function = self.typemap.get(types), оно не сработает, если задействованы подклассы. В этом случае вы , вероятно , придется перебрать typemapиспользованием isinnstance. Интересно, @overload
справится
Я думаю, что это не работает, если функция имеет тип возврата
zenna
1
Это __annotations__ то, dictчто не обеспечивает порядок аргументов, поэтому этот фрагмент кода иногда завершается ошибкой. Я рекомендовал бы изменяя types = tuple(...)к spec = inspect.getfullargspec(function)тогда types = tuple([spec.annotations[x] for x in spec.args]).
xoolive
Вы совершенно правы, @xoolive. Почему бы вам не отредактировать ответ, чтобы добавить свое исправление?
Мухаммед Алкарури
@xoolive: я заметил. Иногда редакторы используют тяжелую руку в управлении правками. Я отредактировал вопрос, чтобы включить ваше исправление. На самом деле, у меня была дискуссия по этому поводу, но нет способа отклонить исправление. Спасибо за помощь, кстати.
Мухаммед Алкарури
22

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

JAB
источник
2
любить это. +1. тем не менее, в конце концов, написание строк документации по-прежнему остается первым способом, которым я делаю свой код читабельным, однако, если бы вы реализовали какую-либо статическую или динамическую проверку, было бы хорошо иметь это. Возможно, я мог бы найти применение для этого.
Уоррен П
8
Я не рекомендую использовать аннотации вместо строк Args: section или @param или аналогичных в ваших строках документации (в любом формате, который вы выберете). В то время как аннотации документации служат хорошим примером, она бросает тень на потенциальную мощь аннотаций, поскольку это может помешать другим более мощным применениям. Кроме того, вы не можете опускать аннотации во время выполнения, чтобы уменьшить потребление памяти (python -OO), как это можно сделать с помощью строк документации и операторов assert.
GPS
2
@ gps: как я уже сказал, это был менее серьезный ответ.
JAB
2
На самом деле, это намного лучший способ документировать типы документов, которые вы ожидаете, но при этом придерживаться DuckTyping.
Марк
1
@gps Я не уверен, что потребление памяти для строк документации вызывает беспокойство в 99,999% случаев.
Томми
13

Когда я впервые увидел аннотации, я подумал: «Отлично! Наконец-то я могу выбрать проверку типов»! Конечно, я не заметил, что аннотации на самом деле не применяются.

Поэтому я решил написать простой декоратор функций для обеспечения их выполнения :

def ensure_annotations(f):
    from functools import wraps
    from inspect import getcallargs
    @wraps(f)
    def wrapper(*args, **kwargs):
        for arg, val in getcallargs(f, *args, **kwargs).items():
            if arg in f.__annotations__:
                templ = f.__annotations__[arg]
                msg = "Argument {arg} to {f} does not match annotation type {t}"
                Check(val).is_a(templ).or_raise(EnsureError, msg.format(arg=arg, f=f, t=templ))
        return_val = f(*args, **kwargs)
        if 'return' in f.__annotations__:
            templ = f.__annotations__['return']
            msg = "Return value of {f} does not match annotation type {t}"
            Check(return_val).is_a(templ).or_raise(EnsureError, msg.format(f=f, t=templ))
        return return_val
    return wrapper

@ensure_annotations
def f(x: int, y: float) -> float:
    return x+y

print(f(1, y=2.2))

>>> 3.2

print(f(1, y=2))

>>> ensure.EnsureError: Argument y to <function f at 0x109b7c710> does not match annotation type <class 'float'>

Я добавил его в библиотеку Ensure .

ткач
источник
У меня такое же разочарование после того, как я был взволнован, веря, что у Python наконец-то появилась проверка типов. В конце концов, придется продолжить выполнение проверки на дому.
Hibou57
3

Прошло много времени с тех пор, как это было задано, но приведенный в вопросе фрагмент примера (как указано там же) взят из PEP 3107, а в конце этого примера PEP также приведены примеры использования, которые могут ответить на вопрос с точки зрения PEP. Посмотреть ;)

Ниже приводится цитата из PEP3107

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

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

  • Предоставление информации о наборе
    • Проверка типа ([3], [4])
    • Пусть IDE покажут, какие типы функция ожидает и возвращает ([17])
    • Перегрузка функций / универсальные функции ([22])
    • Мосты на иностранных языках ([18], [19])
    • Адаптация ([21], [20])
    • Предикатные логические функции
    • Отображение запроса базы данных
    • Маршалинг параметров RPC ([23])
  • Дополнительная информация
    • Документация по параметрам и возвращаемым значениям ([24])

См. ПКП для получения дополнительной информации о конкретных точках (а также их ссылки)

Клаас
источник
Я действительно оценил бы, если downvoters оставляют по крайней мере короткий комментарий, который вызвал downvote. Это действительно поможет (по крайней мере, мне) многое улучшить.
Клаас
2

Python 3.X (только) также обобщает определение функции, позволяя аннотировать аргументы и возвращаемые значения значениями объекта для использования в расширениях. .

Его META-данные, чтобы объяснить, чтобы быть более явным о значениях функции.

Аннотации кодируются как :valueпосле имени аргумента и перед значением по умолчанию, так и как->value после списка аргументов.

Они собраны в __annotations__атрибут функции, но сами Python не рассматривают их как особые:

>>> def f(a:99, b:'spam'=None) -> float:
... print(a, b)
...
>>> f(88)
88 None
>>> f.__annotations__
{'a': 99, 'b': 'spam', 'return': <class 'float'>}

Источник: Python Pocket Reference, пятое издание

ПРИМЕР:

typeannotationsМодуль предоставляет набор инструментов для проверки типа и типа вывода кода Python. Он также предоставляет набор типов, полезных для аннотирования функций и объектов.

Эти инструменты в основном предназначены для использования статическими анализаторами, такими как линтеры, библиотеки автозавершения кода и IDE. Кроме того, предоставляются декораторы для проверки во время выполнения. Проверка типов во время выполнения не всегда хорошая идея в Python, но в некоторых случаях она может быть очень полезной.

https://github.com/ceronman/typeannotations

Как набор текста помогает писать лучший код

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

Чистый Python: элегантное кодирование на Python ISBN: ISBN-13 (pbk): 978-1-4842-4877-5

PEP 526 - Синтаксис для переменных аннотаций

https://www.python.org/dev/peps/pep-0526/

https://www.attrs.org/en/stable/types.html

Демз
источник
@ BlackJack, «для использования в расширениях» не было ясно?
Демз
Это понятно, но не отвечает на вопрос ИМХО. Это все равно, что ответить «Что такое хорошее использование классов?» На «Для использования в программах». Это ясно, правильно, но спрашивающая сторона не очень осведомлена о том, какого черта хорошие конкретные применения. Ваш ответ не может быть более общим, с примером, который по сути такой же, как и тот, который уже был в вопросе .
Блэкджек,
1

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

В настоящее время это никоим образом не применяется, но, исходя из PEP 484, в будущих версиях Python типы будут использоваться только в качестве значения для аннотаций.

Цитирование Как насчет существующего использования аннотаций? :

Мы действительно надеемся, что подсказки типов в конечном итоге станут единственным использованием для аннотаций, но это потребует дополнительного обсуждения и периода устаревания после первоначального развертывания модуля ввода с Python 3.5. Текущий PEP будет иметь предварительный статус (см. PEP 411) до выпуска Python 3.6. Самая быстрая схема, которую можно было бы представить, включала бы тихую отмену аннотаций без подсказок типа в 3.6, полную устаревание в 3.7 и объявляла подсказки типов как единственное разрешенное использование аннотаций в Python 3.8.

Хотя в 3.6 я еще не видел тихой амортизации, это вполне может быть увеличено до 3.7.

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

Димитрис Фасаракис Хиллиард
источник
1

В качестве отложенного ответа некоторые из моих пакетов (marrow.script, WebCore и т. Д.) Используют аннотации, где они доступны, для объявления типов: (например, преобразование входящих значений из Интернета, определение того, какие аргументы являются булевыми переключателями и т. Д.) как выполнить дополнительную разметку аргументов.

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

any_string  # documentation
any_callable  # typecast / callback, not called if defaulting
(any_callable, any_string)  # combination
AnnotationClass()  # package-specific rich annotation object
[AnnotationClass(), AnnotationClass(), …]  # cooperative annotation

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

Отредактировано, чтобы добавить: я также начал использовать пакет TypeGuard , используя для проверки утверждения времени разработки. Преимущество: при запуске с включенной «оптимизацией» ( -O/ PYTHONOPTIMIZEenv var) проверки, которые могут быть дорогостоящими (например, рекурсивными), опускаются, поскольку предполагается, что вы должным образом протестировали свое приложение в процессе разработки, поэтому проверки не должны быть необходимы в производстве.

amcgregor
источник
-2

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

def run(param1: int):
    """
    Does things.

    :param param1: Needed for counting.
    """
    pass

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

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

Лассе Шуирманн
источник
-2

Если вы посмотрите на список преимуществ Cython, основным из них будет возможность сообщить компилятору, какой тип объекта Python.

Я могу представить себе будущее, в котором Cython (или аналогичные инструменты, которые компилируют некоторый ваш код Python) будут использовать синтаксис аннотации, чтобы творить чудеса.

boardrider
источник
RPython Annotator является примером подхода , который чувствует себя соответствующим образом Pythonic; после создания графика вашего приложения оно может определить тип каждой переменной и (для RPython) обеспечить безопасность одного типа. OTOH это требует "бокса" или других решений / обходных путей, чтобы учесть динамические богатые значения. Кто я такой, чтобы заставить мою multiplyфункцию работать только против целых чисел, когда 'na' * 8 + ' batman!'она полностью действительна? ;)
amcgregor