Quali sono gli usi utili delle "Annotazioni di funzioni" di Python3


159

Annotazioni di funzioni: PEP-3107

Mi sono imbattuto in un frammento di codice che mostrava le annotazioni delle funzioni di Python3. Il concetto è semplice ma non riesco a pensare al motivo per cui questi sono stati implementati in Python3 o per qualsiasi buon uso per loro. Forse così mi può illuminare?

Come funziona:

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

Tutto ciò che segue i due punti dopo un argomento è una "annotazione" e le informazioni che seguono ->sono un'annotazione per il valore restituito della funzione.

foo.func_annotations restituirebbe un dizionario:

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

Qual è il significato di avere questo disponibile?



6
@SilentGhost: sfortunatamente, molti dei collegamenti con i casi d'uso reali sono interrotti. Esiste un posto in cui il contenuto potrebbe essere stato archiviato o è andato per sempre?
massimo

16
non dovrebbe foo.func_annotations essere foo.__annotations__in python3?
Zhangxaochen,

2
Le annotazioni non hanno un significato speciale. L'unica cosa che fa Python è metterli nel dizionario delle annotazioni . Ogni altra azione dipende da te.
N Randhawa,

cosa def foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9):significa?
Ali SH

Risposte:


90

Penso che questo sia davvero fantastico.

Proveniente da un background accademico, posso dirti che le annotazioni si sono dimostrate preziose per l'abilitazione di analizzatori statici intelligenti per linguaggi come Java. Ad esempio, è possibile definire semantica come restrizioni di stato, thread a cui è consentito l'accesso, limitazioni di architettura, ecc. E ci sono alcuni strumenti che possono quindi leggerli ed elaborarli per fornire garanzie oltre a ciò che si ottiene dai compilatori. Potresti anche scrivere cose che controllano le precondizioni / postcondizioni.

Sento che qualcosa di simile è particolarmente necessario in Python a causa della sua digitazione più debole, ma in realtà non c'erano costrutti che rendessero questo semplice e parte della sintassi ufficiale.

Vi sono altri usi per le annotazioni oltre la certezza. Vedo come potrei applicare i miei strumenti basati su Java a Python. Ad esempio, ho uno strumento che ti consente di assegnare avvisi speciali ai metodi e ti dà indicazioni quando li chiami che dovresti leggere la loro documentazione (Ad esempio, immagina di avere un metodo che non deve essere invocato con un valore negativo, ma è non intuitivo dal nome). Con le annotazioni, potrei tecnicamente scrivere qualcosa del genere per Python. Allo stesso modo, uno strumento che organizza metodi in una grande classe basata su tag può essere scritto se esiste una sintassi ufficiale.


34
ISTM si tratta di vantaggi teorici che possono essere realizzati solo se la libreria standard e i moduli di terze parti usano tutti le annotazioni delle funzioni e le usano con significato coerente e usano un sistema ben studiato di annotazioni. Fino a quel giorno (che non arriverà mai), gli usi principali delle annotazioni delle funzioni di Python saranno gli usi una tantum descritti nelle altre risposte. Per il momento, puoi dimenticare analizzatori statici intelligenti, garanzie del compilatore, catene di strumenti basate su Java, ecc.
Raymond Hettinger,

4
Anche senza tutto ciò che utilizza le annotazioni delle funzioni, è comunque possibile utilizzarle per l'analisi statica all'interno del codice che le ha sui suoi input e chiama un altro codice che è annotato in modo simile. All'interno di un progetto più ampio o di una base di codice questo potrebbe essere ancora un corpus di codice significativamente utile su cui eseguire analisi statiche basate su annotazioni.
gps

1
AFAICT, puoi fare tutto questo con decoratori, che precedono le annotazioni; pertanto, non vedo ancora il vantaggio. Ho un leggermente diversa su questa questione: stackoverflow.com/questions/13784713/...
allyourcode

9
Avanti rapidamente al 2015, python.org/dev/peps/pep-0484 e mypy-lang.org stanno iniziando a dimostrare che tutti gli oppositori hanno torto.
Mauricio Scheffer,

1
Rivela anche l'influenza di Python su Swift ancora di più.
Uchuugaka,

92

Le annotazioni delle funzioni sono ciò che ne fai.

Possono essere utilizzati per la documentazione:

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

Possono essere utilizzati per il controllo delle condizioni preliminari:

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

Vedi anche http://www.python.org/dev/peps/pep-0362/ per un modo per implementare il controllo del tipo.


18
In che modo è meglio di un docstring per la documentazione o un controllo esplicito del tipo nella funzione? Questo sembra complicare la lingua senza motivo.
endolith,

10
@endolith Possiamo certamente fare a meno delle annotazioni delle funzioni. Forniscono semplicemente un modo standard per accedere alle annotazioni. Ciò li rende accessibili ad help () e ai suggerimenti e li rende disponibili per l'introspezione.
Raymond Hettinger,

4
Invece di passare tra i numeri, è possibile creare tipi Masse Velocityinvece.
destra del

1
per dimostrarlo pienamente, dovrei def kinetic_energy(mass: 'in kilograms', velocity: 'in meters per second') -> float:anche mostrare il tipo restituito. Questa è la mia risposta preferita qui.
Tommy,

Usando il tuo codice, c'è un modo per verificare l' returnannotazione? Non sembra apparire inlocals
user189728

46

Questa è una risposta in ritardo, ma AFAICT, il miglior uso corrente delle annotazioni delle funzioni è PEP-0484 e MyPy .

Mypy è un controllo di tipo statico opzionale per Python. Puoi aggiungere suggerimenti per il tipo ai tuoi programmi Python usando lo standard imminente per le annotazioni sui tipi introdotte in Python 3.5 beta 1 (PEP 484) e usare mypy per controllarli in modo statico.

Usato così:

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
Altri esempi qui Esempi di Mypy e qui Come
El Ruso,

Vedi anche pytype : l'altro analizzatore statico realizzato tenendo presente PEP-0484.
GPS

Sfortunatamente il tipo non è applicato. Se scrivo list(fib('a'))con la tua funzione di esempio, Python 3.7 accetta felicemente l'argomento e si lamenta che non c'è modo di confrontare una stringa e un int.
Denis de Bernardy,

@DenisdeBernardy Come spiega PEP-484, Python fornisce solo annotazioni di tipo. Per imporre i tipi devi usare mypy.
Dustin Wyatt,

23

Solo per aggiungere un esempio specifico di un buon uso dalla mia risposta qui , insieme ai decoratori, può essere fatto un semplice meccanismo per i metodi multipli.

# 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

e un esempio di utilizzo:

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

Questo può essere fatto aggiungendo i tipi al decoratore come mostra il post originale di Guido , ma annotare i parametri stessi è meglio in quanto evita la possibilità di una corrispondenza errata di parametri e tipi.

Nota : in Python è possibile accedere alle annotazioni function.__annotations__anziché a function.func_annotationscome lo func_*stile è stato rimosso su Python 3.


2
Applicazione interessante, anche se temo function = self.typemap.get(types)che non funzionerà quando sono coinvolte le sottoclassi. In tal caso probabilmente dovresti passare in rassegna typemapusando isinnstance. Mi chiedo se lo @overloadgestisce correttamente
Tobias Kienzler,

Penso che questo sia rotto se la funzione ha un tipo di ritorno
zenna

1
Il __annotations__ è una dictche non assicura ordine argomenti, in modo da questo frammento di codice a volte sicuro. Consiglierei di cambiare il types = tuple(...)in spec = inspect.getfullargspec(function)allora types = tuple([spec.annotations[x] for x in spec.args]).
xoolive,

Hai ragione, @xoolive. Perché non modifichi la risposta per aggiungere la tua correzione?
Muhammad Alkarouri,

@xoolive: ho notato. A volte gli editori usano una mano pesante nella gestione delle modifiche. Ho modificato la domanda per includere la tua correzione. In realtà, ho avuto una discussione su questo, ma non c'è modo di annullare la correzione. Grazie per l'aiuto a proposito.
Muhammad Alkarouri,

22

Uri ha già dato una risposta adeguata, quindi eccone una meno seria: così puoi accorciare le tue dotstrings.


2
lo adoro. +1. tuttavia, alla fine, scrivere docstrings è ancora il modo numero uno per rendere leggibile il mio codice, tuttavia, se dovessi implementare qualsiasi tipo di controllo statico o dinamico, è bello averlo. Forse potrei trovare un uso per questo.
Warren P

8
Non consiglio di usare le annotazioni come sostituto di Args: sezione o linee @param o simili nei tuoi documenti (qualunque sia il formato che scegli di usare). Mentre le annotazioni della documentazione rappresentano un bell'esempio, offusca il potenziale potere delle annotazioni in quanto potrebbe interferire con altri usi più potenti. Inoltre, non è possibile omettere le annotazioni in fase di runtime per ridurre il consumo di memoria (python -OO) come è possibile con docstring e dichiarazioni di asserzione.
gps

2
@gps: Come ho detto, è stata una risposta meno seria.
JAB

2
In tutta serietà, è un modo molto migliore per documentare i tipi che ti aspetti, pur aderendo a DuckTyping.
Marc,

1
@gps Non sono sicuro che il consumo di memoria di docstring sia qualcosa di cui preoccuparsi nel 99,999% dei casi.
Tommy

13

La prima volta che ho visto le annotazioni, ho pensato "fantastico! Finalmente posso optare per un controllo del tipo!" Naturalmente, non avevo notato che le annotazioni non sono effettivamente applicate.

Così ho deciso di scrivere un semplice decoratore di funzioni per farli rispettare :

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

L'ho aggiunto alla libreria Assicurare .


Ho la stessa delusione dopo essere stato uscito credendo che Python abbia finalmente il controllo del tipo. Dovrà finalmente proseguire con l'implementazione del controllo del tipo fatto in casa.
Hibou57,

3

È passato molto tempo da quando questo è stato chiesto, ma lo snippet di esempio fornito nella domanda è (come indicato anche lì) da PEP 3107 e alla fine di questo esempio PEP vengono anche forniti casi d'uso che potrebbero rispondere alla domanda dal punto PEP di Visualizza ;)

Quanto segue è citato da PEP3107

Casi d'uso

Nel corso della discussione delle annotazioni, sono stati sollevati numerosi casi d'uso. Alcuni di questi sono presentati qui, raggruppati in base al tipo di informazioni che trasmettono. Sono inclusi anche esempi di prodotti e pacchetti esistenti che potrebbero utilizzare le annotazioni.

  • Fornire informazioni di battitura
    • Controllo del tipo ([3], [4])
    • Consenti agli IDE di mostrare quali tipi si aspetta e restituisce una funzione ([17])
    • Sovraccarico di funzioni / funzioni generiche ([22])
    • Ponti in lingua straniera ([18], [19])
    • Adattamento ([21], [20])
    • Funzioni logiche dedicate
    • Mappatura delle query del database
    • Marshalling dei parametri RPC ([23])
  • Altre informazioni
    • Documentazione per parametri e valori di ritorno ([24])

Vedi PEP per maggiori informazioni su punti specifici (così come i loro riferimenti)


Gradirei davvero se i downvoter lasciano almeno un breve commento su ciò che ha causato il downvote. Questo aiuterebbe davvero (almeno io) molto a migliorare.
klaas,

2

Python 3.X (solo) generalizza anche la definizione della funzione per consentire l'annotazione di argomenti e valori di ritorno con valori di oggetto da utilizzare nelle estensioni .

I suoi dati META da spiegare, per essere più espliciti sui valori delle funzioni.

Le annotazioni sono codificate come :valuedopo il nome dell'argomento e prima di un valore predefinito e come ->valuedopo l'elenco degli argomenti.

Sono raccolti in un __annotations__attributo della funzione, ma non vengono altrimenti trattati come speciali dallo stesso 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'>}

Fonte: Python Pocket Reference, Quinta Edizione

ESEMPIO:

Il typeannotationsmodulo fornisce una serie di strumenti per il controllo del tipo e l'inferenza del tipo del codice Python. Fornisce inoltre una serie di tipi utili per annotare funzioni e oggetti.

Questi strumenti sono progettati principalmente per essere utilizzati da analizzatori statici come linter, librerie di completamento del codice e IDE. Inoltre, vengono forniti decoratori per effettuare controlli di runtime. Il controllo del tipo di runtime non è sempre una buona idea in Python, ma in alcuni casi può essere molto utile.

https://github.com/ceronman/typeannotations

Come la digitazione aiuta a scrivere codice migliore

La digitazione può aiutarti a fare analisi del codice statico per rilevare errori di tipo prima di inviare il codice alla produzione e impedirti alcuni ovvi bug. Ci sono strumenti come Mypy, che puoi aggiungere alla tua cassetta degli attrezzi come parte del tuo ciclo di vita del software. mypy può controllare i tipi corretti eseguendo il tuo codebase parzialmente o completamente. mypy ti aiuta anche a rilevare bug come il controllo del tipo Nessuno quando il valore viene restituito da una funzione. La digitazione aiuta a rendere il codice più pulito. Invece di documentare il codice utilizzando i commenti, in cui si specificano i tipi in un docstring, è possibile utilizzare i tipi senza alcun costo per le prestazioni.

Python pulito: codifica elegante in Python ISBN: ISBN-13 (pbk): 978-1-4842-4877-5

PEP 526 - Sintassi per annotazioni variabili

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

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


@ Black Jack, il "per l'uso nelle estensioni" non era chiaro?
Il Demz il

È chiaro, ma non risponde alla domanda IMHO. È come rispondere "Quali sono i buoni usi delle classi?" Con "Per l'uso nei programmi." È chiaro, corretto, ma la parte che chiede non è molto più saggia su cosa diavolo sono i buoni usi concreti . La tua è una risposta che non può essere più generica, con un esempio sostanzialmente uguale a quello già nella domanda .
BlackJack,

1

Nonostante tutti gli usi descritti qui, quello applicabile e, molto probabilmente, forzato delle annotazioni sarà per i suggerimenti sul tipo .

Questo non è attualmente applicato in alcun modo ma, a giudicare da PEP 484, le versioni future di Python consentiranno solo i tipi come valore per le annotazioni.

Citazioni Che dire degli usi esistenti delle annotazioni? :

Speriamo che i suggerimenti sul tipo diventino alla fine l'unico uso per le annotazioni, ma ciò richiederà ulteriori discussioni e un periodo di deprecazione dopo il lancio iniziale del modulo di digitazione con Python 3.5. L'attuale PEP avrà uno stato provvisorio (vedi PEP 411) fino al rilascio di Python 3.6. Lo schema concepibile più veloce introdurrebbe l'ammortamento silenzioso delle annotazioni non di tipo suggerimento in 3.6, l'ammortamento completo in 3.7 e dichiarerebbe i suggerimenti di tipo come l'unico uso consentito delle annotazioni in Python 3.8.

Anche se non ho ancora visto alcun deprezzamento silenzioso in 3.6, questo potrebbe essere portato a 3.7, invece.

Quindi, anche se potrebbero esserci altri buoni casi d'uso, è meglio tenerli solo per suggerimenti sul tipo se non si vuole andare in giro a cambiare tutto in un futuro in cui è presente questa restrizione.


1

Come risposta ritardata, molti dei miei pacchetti (marrow.script, WebCore, ecc.) Usano le annotazioni ove disponibili per dichiarare il typecasting (cioè trasformare i valori in arrivo dal web, rilevare quali argomenti sono switch booleani, ecc.) come eseguire un markup aggiuntivo di argomenti.

Marrow Script crea un'interfaccia a riga di comando completa per funzioni e classi arbitrarie e consente di definire la documentazione, il cast e i valori predefiniti derivati ​​dalla callback tramite annotazioni, con un decoratore per supportare i runtime più vecchi. Tutte le mie librerie che utilizzano le annotazioni supportano i moduli:

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

Il supporto "nudo" per docstring o funzioni di typecasting facilita la miscelazione con altre librerie che sono a conoscenza delle annotazioni. (Vale a dire un controller Web che utilizza il typecasting che viene anche esposto come uno script da riga di comando.)

Modificato per aggiungere: ho anche iniziato a utilizzare il pacchetto TypeGuard usando asserzioni in fase di sviluppo per la convalida. Vantaggio: se eseguiti con "ottimizzazioni" abilitate ( -O/ PYTHONOPTIMIZEenv var) i controlli, che possono essere costosi (ad esempio ricorsivi), vengono omessi, con l'idea di aver testato correttamente l'app in fase di sviluppo, quindi i controlli non dovrebbero essere necessari in produzione.


-2

Le annotazioni possono essere utilizzate per modulare facilmente il codice. Ad esempio un modulo per un programma che sto mantenendo potrebbe semplicemente definire un metodo come:

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

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

e potremmo chiedere all'utente una cosa chiamata "param1" che è "Necessario per il conteggio" e dovrebbe essere un "int". Alla fine possiamo persino convertire la stringa fornita dall'utente nel tipo desiderato per ottenere l'esperienza senza problemi.

Vedi il nostro oggetto metadati di funzione per una classe open source che aiuta con questo e può recuperare automaticamente i valori necessari e convertirli in qualsiasi tipo desiderato (perché l'annotazione è un metodo di conversione). Perfino gli IDE mostrano i completamenti automatici giusti e presumono che i tipi siano conformi alle annotazioni: una misura perfetta.


-2

Se guardi l'elenco dei vantaggi di Cython, uno importante è la capacità di dire al compilatore che tipo è un oggetto Python.

Posso immaginare un futuro in cui Cython (o strumenti simili che compilano parte del tuo codice Python) useranno la sintassi delle annotazioni per fare la loro magia.


L' annotatore di RPython è un esempio di approccio che sembra opportunamente Pythonic; dopo aver generato un grafico della tua applicazione, può capire il tipo di ogni variabile e (per RPython) applicare la sicurezza di tipo singolo. OTOH richiede "boxe" o altre soluzioni / soluzioni alternative per consentire valori ricchi dinamici. Chi sono io per forzare la mia multiplyfunzione a lavorare solo contro numeri interi, quando 'na' * 8 + ' batman!'è del tutto valida? ;)
amcgregor
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.