Annotazioni di funzioni di Python 3

Le annotazioni delle funzioni sono una funzione di Python 3 che consente di aggiungere metadati arbitrari per gli argomenti della funzione e il valore di ritorno. Facevano parte delle specifiche originali di Python 3.0.

In questo tutorial ti mostrerò come sfruttare le annotazioni delle funzioni generiche e combinarle con i decoratori. Imparerai anche i pro ei contro delle annotazioni delle funzioni, quando è opportuno utilizzarle e quando è meglio usare altri meccanismi come docstring e semplici decoratori.

Annotazioni di funzioni

Le annotazioni delle funzioni sono specificate in PEP-3107. La motivazione principale era fornire un modo standard per associare i metadati agli argomenti della funzione e al valore di ritorno. Molti membri della community hanno trovato nuovi casi d'uso, ma hanno utilizzato diversi metodi come decoratori personalizzati, formati di docstring personalizzati e aggiunta di attributi personalizzati all'oggetto funzione.

È importante capire che Python non benedica le annotazioni con alcuna semantica. Fornisce un semplice supporto sintattico per associare i metadati e un modo semplice per accedervi. Inoltre, le annotazioni sono totalmente opzionali.

Diamo un'occhiata a un esempio. Ecco una funzione foo () che prende tre argomenti chiamati a, bec e stampa la loro somma. Nota che foo () non restituisce nulla. Il primo argomento un non è annotato. Il secondo argomento B è annotato con la stringa "annotating b" e il terzo argomento c è annotato con tipo int. Il valore di ritorno è annotato con il tipo galleggiante. Notare la sintassi "->" per annotare il valore restituito.

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

Le annotazioni non hanno alcun impatto sull'esecuzione della funzione. Chiamiamo foo () due volte: una volta con argomenti int e una volta con argomenti stringa. In entrambi i casi, foo () fa la cosa giusta e le annotazioni sono semplicemente ignorate.

"python foo ('Ciao', ',', 'Mondo!') Ciao, Mondo!

foo (1, 2, 3) 6 "

Argomenti predefiniti

Gli argomenti predefiniti sono specificati dopo l'annotazione:

"python def foo (x: 'un argomento che ha come valore predefinito 5' = 5): print (x)

pippo (7) 7

foo () 5 "

Accedere alle annotazioni delle funzioni

L'oggetto funzione ha un attributo chiamato 'annotazioni'. È una mappatura che associa ogni nome di argomento alla sua annotazione. L'annotazione del valore di ritorno è mappata sulla chiave "return", che non può essere in conflitto con alcun nome di argomento poiché "return" è una parola riservata che non può servire come nome di argomento. Si noti che è possibile passare un argomento parola chiave denominato return a una funzione:

"python def bar (* args, ** kwargs: 'gli argomenti delle parole chiave dict'): print (kwargs ['return'])

d = 'return': 4 bar (** d) 4 "

Torniamo al nostro primo esempio e controlliamo le sue annotazioni:

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

stampa (foo.annotazioni) 'c': , "b": "annotating b", "return": "

Questo è piuttosto semplice. Se si annota una funzione con una matrice di argomenti e / o una matrice di argomenti di parole chiave, ovviamente non è possibile annotare singoli argomenti.

"python def foo (* args: 'lista di argomenti senza nome', ** kwargs: 'dict of named arguments'): print (args, kwargs)

stampa (foo.annotazioni) 'args': 'lista di argomenti senza nome', 'kwargs': 'dict of named arguments' ""

Se si legge la sezione sull'accesso alle annotazioni delle funzioni in PEP-3107, si dice che le si accede tramite l'attributo 'func_annotations' dell'oggetto funzione. Questo non è aggiornato a partire da Python 3.2. Non essere confuso. È semplicemente il 'annotazioni'attributo.

Cosa puoi fare con le annotazioni?

Questa è la grande domanda. Le annotazioni non hanno significato o semantica standard. Esistono diverse categorie di usi generici. Puoi usarli come documentazione migliore e spostare argomenti e restituire la documentazione del valore dalla docstring. Ad esempio, questa funzione:

python def div (a, b): "" "Dividi a per b arg: a - il dividendo b - il divisore (deve essere diverso da 0) restituisce: il risultato della divisione di a per b" "" restituisce a / b

Può essere convertito in:

python def div (a: 'the dividend', b: 'il divisore (deve essere diverso da 0)') -> 'il risultato della divisione di a da b': "" "Divide a da b" "" return a / B

Mentre vengono acquisite le stesse informazioni, ci sono molti vantaggi nella versione delle annotazioni:

  1. Se si rinomina un argomento, la versione docstring della documentazione potrebbe non essere aggiornata.
  2. È più facile vedere se un argomento non è documentato.
  3. Non è necessario elaborare un formato speciale di documentazione degli argomenti all'interno della docstring per poter essere analizzato dagli strumenti. Il annotazioni attributo fornisce un meccanismo di accesso diretto e standard.

Un altro utilizzo di cui parleremo in seguito è la digitazione opzionale. Python è digitato in modo dinamico, il che significa che puoi passare qualsiasi oggetto come argomento di una funzione. Ma spesso le funzioni richiedono che gli argomenti siano di un tipo specifico. Con le annotazioni puoi specificare il tipo proprio accanto all'argomento in modo molto naturale.

Ricorda che solo la specifica del tipo non lo imporrà e sarà necessario un ulteriore lavoro (molto lavoro). Tuttavia, anche solo la specifica del tipo può rendere l'intento più leggibile rispetto alla specifica del tipo nella docstring e può aiutare gli utenti a capire come chiamare la funzione.

Un altro vantaggio delle annotazioni su docstring è che è possibile allegare diversi tipi di metadati come tuple o dict. Ancora, puoi farlo anche con docstring, ma sarà basato sul testo e richiederà un'analisi speciale.

Infine, puoi allegare molti metadati che verranno utilizzati da strumenti esterni speciali o in fase di esecuzione tramite decoratori. Esplorerò questa opzione nella prossima sezione.

Più annotazioni

Supponiamo di voler annotare un argomento con il suo tipo e una stringa di aiuto. Questo è molto facile con le annotazioni. Puoi semplicemente annotare l'argomento con un dict che ha due chiavi: 'type' e 'help'.

"python def div (a: dict (type = float, help =" il dividendo "), b: dict (type = float, help =" il divisore (deve essere diverso da 0) ")) -> dict (type = float, help = "il risultato della divisione di a da b"): "" "Dividi a da b" "" restituisci a / b

stampa (div.annotazioni) 'a': 'help': 'the dividend', 'type': float, 'b': 'help': 'il divisore (deve essere diverso da 0)', 'type': float , 'return': 'help': 'il risultato della divisione di a da b', 'tipo': float "

Combinazione di annotazioni e decoratori Python

Annotazioni e decoratori vanno di pari passo. Per una buona introduzione ai decoratori Python, dai un'occhiata ai miei due tutorial: Deep Dive Into Python Decorators e Write Your Own Python Decorators.

Innanzitutto, le annotazioni possono essere pienamente implementate come decoratori. Puoi solo definire un @annotare decoratore e deve prendere un nome argomento e un'espressione Python come argomenti e quindi memorizzarli nella funzione di destinazione annotazioni attributo. Questo può essere fatto anche per Python 2.

Tuttavia, il vero potere dei decoratori è che possono agire sulle annotazioni. Ciò richiede il coordinamento, ovviamente, sulla semantica delle annotazioni.

Diamo un'occhiata a un esempio. Supponiamo di voler verificare che gli argomenti siano in un certo intervallo. L'annotazione sarà una tupla con il valore minimo e massimo per ogni argomento. Quindi abbiamo bisogno di un decoratore che verificherà l'annotazione di ogni argomento di parole chiave, verificherà che il valore rientri nell'intervallo e sollevi un'eccezione altrimenti. Iniziamo con il decoratore:

python def check_range (f): def decorato (* args, ** kwargs): per nome, intervallo in f .__ annotazioni __. items (): min_value, max_value = range se non (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

Ora, definiamo la nostra funzione e decoriamo con il @check_range decoratori.

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

Chiamiamo foo () con argomenti diversi e vedere cosa succede. Quando tutti gli argomenti sono nel loro raggio, non c'è nessun problema.

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

Ma se impostiamo c a 100 (al di fuori dell'intervallo (10, 20)) viene sollevata un'eccezione:

python foo (a = 4, b = 6, c = 100) ValueError: argomento c non compreso nell'intervallo [10 - 20]

Quando dovresti usare Decoratori invece di annotazioni?

Ci sono diverse situazioni in cui i decoratori sono migliori delle annotazioni per allegare i metadati.

Un caso ovvio è se il tuo codice deve essere compatibile con Python 2.

Un altro caso è se possiedi molti metadati. Come hai visto prima, mentre è possibile allegare qualsiasi quantità di metadati usando dicts come annotazioni, è piuttosto ingombrante e in realtà fa male la leggibilità.

Infine, se i metadati devono essere gestiti da un decoratore specifico, potrebbe essere meglio associare i metadati come argomenti per il decoratore stesso.

Annotazioni dinamiche

Le annotazioni sono solo un attributo dict di una funzione.

tipo python (foo .__ annotations__) dict

Ciò significa che è possibile modificarli al volo mentre il programma è in esecuzione. Quali sono alcuni casi d'uso? Supponiamo di voler scoprire se viene mai usato un valore predefinito di un argomento. Ogni volta che la funzione viene chiamata con il valore predefinito, è possibile incrementare il valore di un'annotazione. O forse vuoi riassumere tutti i valori di ritorno. L'aspetto dinamico può essere fatto all'interno della funzione stessa o da un decoratore.

"python def add (a, b) -> 0: result = a + b add.annotazioni['return'] + = risultato di ritorno del risultato

stampa (aggiungi.annotazioni['return']) 0

aggiungi (3, 4) 7 stampa (aggiungi.annotazioni['return']) 7

aggiungi (5, 5) 10 stampa (aggiungi.annotazioni['return']) 17 "

Conclusione

Le annotazioni sulla funzione sono versatili ed eccitanti. Hanno il potenziale per inaugurare una nuova era di strumenti introspettivi che aiutano gli sviluppatori a padroneggiare sistemi sempre più complessi. Offrono allo sviluppatore più avanzato un modo standard e leggibile per associare i metadati direttamente agli argomenti e al valore di ritorno, al fine di creare strumenti personalizzati e interagire con i decoratori. Ma ci vuole un po 'di lavoro per trarne beneficio e sfruttare il loro potenziale.

Impara Python

Impara Python con la nostra guida completa al tutorial su Python, sia che tu stia appena iniziando o che sei un programmatore esperto che cerca di imparare nuove abilità.