Які корисні користі для «Анотацій функції» 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}

Яке значення має це доступність?



6
@SilentGhost: на жаль, багато зв’язків із фактичними випадками використання порушені. Чи є якесь місце, де вміст може бути збережений, або він назавжди зник?
макс

16
не повинно foo.func_annotations бути foo.__annotations__в python3?
zhangxaochen

2
Анотації не мають особливих значень. Єдине, що робить Python - це помістити їх у словник анотацій . Будь-яка інша дія залежить від вас.
N Randhawa

що def foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9):означає?
Алі Ш.

Відповіді:


90

Я думаю, що це насправді чудово.

Виходячи з академічного походження, я можу вам сказати, що анотації виявилися безцінними для включення розумних статичних аналізаторів для таких мов, як Java. Наприклад, ви можете визначити семантику, як обмеження стану, потоки, яким дозволено доступ, обмеження архітектури тощо, і є досить багато інструментів, які потім можуть їх прочитати та обробити, щоб забезпечити впевненість, що ви отримуєте від компіляторів. Ви навіть можете написати речі, які перевіряють передумови / пост-умови.

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

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


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 на Свіфта.
uchuugaka

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
Як це краще, ніж docstring для документації, або явна перевірка типу у функції? Це, здається, ускладнює мову без будь-яких причин.
ендоліт

10
@endolith Ми, звичайно, не можемо обійтися без приміток щодо функцій. Вони просто пропонують стандартний спосіб доступу до приміток. Це робить їх доступними для допомоги () та підказок інструментів та робить їх доступними для самоаналізу.
Реймонд Хеттінгер

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
Більше прикладів тут Приклади Міпі та тут, як можна скористатися
El Ruso

Також дивіться пітип - інший статичний аналізатор, що будується з урахуванням 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

Урі вже дав належну відповідь, тож ось менш серйозний: Отже, ви можете зробити свої доктрини коротшими.


2
любити це. +1. однак, врешті-решт, написання документів є все-таки номером один із способів зробити свій код читабельним, однак, якщо ви застосовували будь-яку статичну або динамічну перевірку, приємно це мати. Можливо, я можу знайти для цього користь.
Warren P

8
Я не рекомендую використовувати анотації як заміну для аргументів: розділ або рядки @param або подібні у ваших документах (незалежно від формату, який ви використовуєте). Хоча анотації до документації є гарним прикладом, вона загрожує потенційною силою анотацій, оскільки вона може стати на шляху інших потужних застосувань. Крім того, ви не можете опускати анотації під час виконання, щоб зменшити споживання пам’яті (python -OO), як це можна з документами та твердженнями заяви.
gps

2
@gps: Як я вже сказав, це була менш серйозна відповідь.
JAB

2
По суті, це набагато кращий спосіб документувати типи, які ви очікуєте, зберігаючи DuckTyping.
Марк

1
@gps Я не впевнений, що в 99,999% випадків споживання пам'яті docstrings турбує щось.
Томмі

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'>

Я додав його до бібліотеки " Забезпечити" .


У мене таке ж розчарування після того, як я був впевнений, що, нарешті, Python нарешті має перевірку типу. Нарешті, доведеться продовжувати роботу з власною реалізацією перевірки типу.
Hibou57

3

З давнього часу це було задано, але приклад фрагменту, наведеного у запитанні, є (як це також зазначено там) з PEP 3107 і наприкінці цього прикладу PEP також наведені випадки використання, які можуть відповісти на питання з точки зору PEPs перегляд;)

Далі цитується PEP3107

Використовуйте випадки

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

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

Дивіться PEP для отримання додаткової інформації про конкретні моменти (а також їх посилання)


Я дійсно буду вдячний, якщо питомники залишать хоча б короткий коментар, що спричинило породження. Це дійсно допоможе (принаймні мені) багато чого покращити.
klaas

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, коли значення повертається з функції. Введення тексту допомагає зробити ваш код більш чистим. Замість документування коду за допомогою коментарів, де ви вказуєте типи в docstring, ви можете використовувати типи без будь-якої вартості продуктивності.

Clean 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, "для використання в розширеннях" було не ясно?
Демз

Це зрозуміло, але не відповідає на питання ІМХО. Це як відповідати «Які гарні використання класів?» З «Для використання в програмах.» Це ясно, правильно, але запитувана сторона не дійсно мудрішими щодо того , що клямка хороші конкретні види використання. Ваші відповіді не можуть бути більш загальними, приклад, який по суті такий же, як той, що вже в запитанні .
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

«Гола» підтримка функцій docstrings або typecasting дозволяє легше змішуватись з іншими бібліотеками, які відомі анотаціям. (У мене є веб-контролер, що використовує typecasting, який також може бути викритий як сценарій командного рядка.)

Відредаговано, щоб додати: Я також почав використовувати пакет TypeGuard, використовуючи твердження про час розробки для перевірки. Перевага: при запуску з увімкненими "оптимізаціями" ( -O/ PYTHONOPTIMIZEenv var) чеки, які можуть бути дорогими (наприклад, рекурсивними), опускаються, з ідеєю, що ви належним чином перевірили додаток у розробці, тому перевірки повинні бути непотрібними у виробництві.


-2

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

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

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

і ми могли б попросити у користувача те, що називається "param1", яке "Потрібно для підрахунку" і має бути "int". Врешті-решт, ми можемо навіть перетворити рядок, наданий користувачем, у потрібний тип, щоб отримати найбільше клопоту.

Дивіться наш об’єкт метаданих функцій щодо класу з відкритим кодом, який допомагає в цьому і може автоматично отримати необхідні значення та перетворити їх у будь-який бажаний тип (адже анотація - це метод перетворення). Навіть IDE правильно показують автозавершення та припускають, що типи відповідають анотаціям - ідеально підходить.


-2

Якщо ви подивитесь на перелік переваг Cython, головне - це можливість сказати компілятору, який тип об’єкта Python є.

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


RPython Annotator є прикладом підходу , який відчуває себе відповідним чином Pythonic; після генерування графіку вашої програми він може розробити тип кожної змінної та (для RPython) забезпечити безпеку одного типу. Для іншого він вимагає "боксу" або інших рішень / заощаджень, щоб забезпечити динамічні величини. Хто я, щоб змусити свою multiplyфункцію працювати лише проти цілих чисел, коли 'na' * 8 + ' batman!'цілком справедливо? ;)
amcgregor
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.