Cosa sono i suggerimenti sul tipo in Python 3.5?


250

Una delle funzionalità più discusse in Python 3.5 sono i suggerimenti sul tipo .

Un esempio di suggerimenti sul tipo è menzionato in questo articolo e in questo mentre si parla anche di usare i suggerimenti sul tipo in modo responsabile. Qualcuno può spiegare di più su di loro e quando dovrebbero essere usati e quando no?


4
Dai un'occhiata al PEP 484 che è collegato dal log delle modifiche ufficiale .
Stefan,

1
@AvinashRaj: una buona discussione sulle uscite è in corso qui
Vaulstein

1
È un peccato che il caso d'uso C-API sia completamente ignorato da questo PEP 484, in particolare i suggerimenti sul tipo per Cython e Numba.
denfromufa,

Risposte:


343

Suggerirei di leggere PEP 483 e PEP 484 e di guardare questa presentazione di Guido su Type Hinting.

In breve : il suggerimento per tipo è letteralmente il significato delle parole, accenni al tipo di oggetto / i che si sta utilizzando .

A causa della natura dinamica di Python, dedurre o verificare il tipo di un oggetto utilizzato è particolarmente difficile. Questo fatto rende difficile per gli sviluppatori capire cosa sta succedendo esattamente nel codice che non hanno scritto e, soprattutto, per gli strumenti di controllo del tipo trovati in molti IDE [PyCharm, PyDev vengono in mente] che sono limitati dal fatto che non hanno alcun indicatore del tipo di oggetti. Di conseguenza ricorrono a provare a inferire il tipo con (come menzionato nella presentazione) un tasso di successo del 50% circa.


Per prendere due diapositive importanti dalla presentazione Suggerimenti sul tipo:

Perché digitare suggerimenti?

  1. Aiuta a controllare i tipi: indicando quale tipo vuoi che l'oggetto sia il controllo dei tipi può facilmente rilevare se, ad esempio, stai passando un oggetto con un tipo non previsto.
  2. Aiuta con la documentazione: una terza persona che visualizza il tuo codice saprà cosa ci si aspetta dove, ergo, come usarlo senza ottenerlo TypeErrors.
  3. Aiuta gli IDE a sviluppare strumenti più precisi e robusti: gli ambienti di sviluppo saranno più adatti a suggerire metodi appropriati quando conoscono il tipo di oggetto. Probabilmente lo hai sperimentato con qualche IDE ad un certo punto, colpendo il .e avendo pop-up metodi / attributi che non sono definiti per un oggetto.

Perché usare i controllori statici?

  • Trova i bug prima : questo è evidente, credo.
  • Più grande è il tuo progetto, più ne avrai bisogno : Ancora una volta, ha senso. Le lingue statiche offrono una solidità e un controllo che mancano alle lingue dinamiche. Più grande e complessa è l'applicazione, maggiore è il controllo e la prevedibilità (da un aspetto comportamentale) necessari.
  • I team di grandi dimensioni stanno già eseguendo analisi statiche : immagino che questo verifichi i primi due punti.

Come nota di chiusura di questa piccola introduzione : questa è una funzione opzionale e, da quello che ho capito, è stata introdotta per trarre alcuni vantaggi dalla tipizzazione statica.

In genere non è necessario preoccuparsene e sicuramente non è necessario utilizzarlo (soprattutto nei casi in cui si utilizza Python come linguaggio di scripting ausiliario). Dovrebbe essere utile quando si sviluppano grandi progetti in quanto offre robustezza, controllo e capacità di debug aggiuntive .


Tipo Suggerimento con mypy :

Per rendere questa risposta più completa, penso che una piccola dimostrazione sarebbe adatta. Userò mypyla libreria che ha ispirato i suggerimenti di tipo quando sono presentati nel PEP. Questo è scritto principalmente per chiunque si imbattesse in questa domanda e si chiedesse da dove cominciare.

Prima di farlo, lasciatemi ripetere quanto segue: PEP 484 non impone nulla; sta semplicemente impostando una direzione per le annotazioni delle funzioni e proponendo linee guida su come il controllo del tipo può / dovrebbe essere eseguito. Puoi annotare le tue funzioni e suggerire tutte le cose che vuoi; i tuoi script verranno comunque eseguiti indipendentemente dalla presenza di annotazioni perché Python stesso non le utilizza.

Ad ogni modo, come osservato nel PEP, i tipi di suggerimento dovrebbero generalmente assumere tre forme:

Inoltre, ti consigliamo di utilizzare i suggerimenti sul tipo insieme al nuovo typingmodulo introdotto in Py3.5. In esso, molti (ulteriori) ABC (classi di base astratte) sono definiti insieme a funzioni di supporto e decoratori per l'uso nel controllo statico. La maggior parte ABCsin collections.abcsono inclusi, ma in un Genericmodulo al fine di consentire di sottoscrizione (attraverso la definizione di un __getitem__()metodo).

Per chiunque sia interessato a una spiegazione più approfondita di questi, mypy documentationè scritto molto bene e ha molti esempi di codice che dimostrano / descrivono la funzionalità del loro correttore; merita sicuramente una lettura.

Annotazioni di funzioni e commenti speciali:

Innanzitutto, è interessante osservare alcuni dei comportamenti che possiamo ottenere usando commenti speciali. # type: typeCommenti speciali possono essere aggiunti durante le assegnazioni di variabili per indicare il tipo di un oggetto se non si può dedurre direttamente. I compiti semplici sono generalmente facilmente deducibili, ma altri, come le liste (per quanto riguarda il loro contenuto), non possono.

Nota: se vogliamo utilizzare qualsiasi derivato di Containerse dobbiamo specificare i contenuti per quel contenitore, dobbiamo usare i tipi generici dal typingmodulo. Questi supportano l'indicizzazione.

# generic List, supports indexing.
from typing import List

# In this case, the type is easily inferred as type: int.
i = 0

# Even though the type can be inferred as of type list
# there is no way to know the contents of this list.
# By using type: List[str] we indicate we want to use a list of strings.
a = []  # type: List[str]

# Appending an int to our list
# is statically not correct.
a.append(i)

# Appending a string is fine.
a.append("i")

print(a)  # [0, 'i']

Se aggiungiamo questi comandi a un file e li eseguiamo con il nostro interprete, tutto funziona perfettamente e print(a)stampa il contenuto dell'elenco a. I # typecommenti sono stati scartati, trattati come semplici commenti che non hanno alcun significato semantico aggiuntivo .

Eseguendo questo con mypy, d'altra parte, otteniamo la seguente risposta:

(Python3)jimmi@jim: mypy typeHintsCode.py
typesInline.py:14: error: Argument 1 to "append" of "list" has incompatible type "int"; expected "str"

Indica che un elenco di stroggetti non può contenere un intche, staticamente parlando, è valido. Ciò può essere risolto rispettando il tipo di ae solo aggiungendo stroggetti o modificando il tipo di contenuto di aper indicare che qualsiasi valore è accettabile (eseguito in modo intuitivo List[Any]dopo che Anyè stato importato da typing).

Le annotazioni delle funzioni vengono aggiunte nel modulo param_name : typedopo ogni parametro nella firma della funzione e viene specificato un tipo restituito utilizzando la -> typenotazione prima dei due punti della funzione finale; tutte le annotazioni sono memorizzate __annotations__nell'attributo per quella funzione in un comodo modulo dizionario. Usando un esempio banale (che non richiede tipi extra dal typingmodulo):

def annotated(x: int, y: str) -> bool:
    return x < y

L' annotated.__annotations__attributo ora ha i seguenti valori:

{'y': <class 'str'>, 'return': <class 'bool'>, 'x': <class 'int'>}

Se siamo un noobie completo, o abbiamo familiarità con i Py2.7concetti e di conseguenza non siamo a conoscenza del TypeErrornascondiglio nel confronto di annotated, possiamo eseguire un altro controllo statico, rilevare l'errore e salvarci qualche problema:

(Python3)jimmi@jim: mypy typeHintsCode.py
typeFunction.py: note: In function "annotated":
typeFunction.py:2: error: Unsupported operand types for > ("str" and "int")

Tra le altre cose, anche la chiamata alla funzione con argomenti non validi verrà catturata:

annotated(20, 20)

# mypy complains:
typeHintsCode.py:4: error: Argument 2 to "annotated" has incompatible type "int"; expected "str"

Questi possono essere estesi praticamente a qualsiasi caso d'uso e gli errori rilevati vanno oltre le chiamate e le operazioni di base. I tipi che puoi verificare sono davvero flessibili e ho semplicemente dato un piccolo assaggio del suo potenziale. Uno sguardo al typingmodulo, ai PEP o ai mypydocumenti ti darà un'idea più completa delle capacità offerte.

File stub:

I file stub possono essere utilizzati in due diversi casi non reciprocamente esclusivi:

  • È necessario digitare check un modulo per il quale non si desidera modificare direttamente le firme delle funzioni
  • Vuoi scrivere moduli e avere il controllo del tipo ma vuoi anche separare le annotazioni dal contenuto.

I file stub (con estensione di .pyi) sono un'interfaccia annotata del modulo che si sta creando / si desidera utilizzare. Contengono le firme delle funzioni che si desidera verificare con il corpo delle funzioni scartate. Per avere un'idea di ciò, dato un insieme di tre funzioni casuali in un modulo chiamato randfunc.py:

def message(s):
    print(s)

def alterContents(myIterable):
    return [i for i in myIterable if i % 2 == 0]

def combine(messageFunc, itFunc):
    messageFunc("Printing the Iterable")
    a = alterContents(range(1, 20))
    return set(a)

Possiamo creare un file stub randfunc.pyi, in cui possiamo porre alcune restrizioni se lo desideriamo. Il rovescio della medaglia è che qualcuno che visualizza la fonte senza lo stub non otterrà realmente l'assistenza di annotazione quando cerca di capire cosa dovrebbe essere passato dove.

Ad ogni modo, la struttura di un file stub è piuttosto semplicistica: aggiungi tutte le definizioni delle funzioni con corpi vuoti ( passriempiti) e fornisci le annotazioni in base alle tue esigenze. Qui, supponiamo di voler lavorare solo con i inttipi per i nostri Container.

# Stub for randfucn.py
from typing import Iterable, List, Set, Callable

def message(s: str) -> None: pass

def alterContents(myIterable: Iterable[int])-> List[int]: pass

def combine(
    messageFunc: Callable[[str], Any],
    itFunc: Callable[[Iterable[int]], List[int]]
)-> Set[int]: pass

La combinefunzione fornisce un'indicazione del motivo per cui potresti voler utilizzare le annotazioni in un altro file, a volte ingombrano il codice e riducono la leggibilità (grande no-no per Python). Ovviamente potresti usare gli alias di tipo, ma a volte confonde più di quanto aiuti (quindi usali saggiamente).


Questo dovrebbe farti familiarizzare con i concetti di base dei suggerimenti di tipo in Python. Anche se lo strumento di verifica del tipo utilizzato è stato mypy, dovresti gradualmente iniziare a vederne altri pop-up, alcuni internamente negli IDE ( PyCharm ) e altri come moduli standard di Python. Proverò ad aggiungere ulteriori checker / pacchetti correlati nel seguente elenco quando e se li trovo (o se suggerito).

Dama che conosco :

  • Mypy : come descritto qui.
  • PyType : da Google, usa una notazione diversa da quella che raccolgo, probabilmente vale la pena dare un'occhiata.

Pacchetti / progetti correlati :

  • typeshed: repository Python ufficiale contenente un assortimento di file stub per la libreria standard.

Il typeshedprogetto è in realtà uno dei posti migliori in cui puoi guardare per vedere come il suggerimento tipo potrebbe essere usato in un tuo progetto. Prendiamo ad esempio i __init__dunders della Counterclasse nel .pyifile corrispondente :

class Counter(Dict[_T, int], Generic[_T]):
        @overload
        def __init__(self) -> None: ...
        @overload
        def __init__(self, Mapping: Mapping[_T, int]) -> None: ...
        @overload
        def __init__(self, iterable: Iterable[_T]) -> None: ...

Dove _T = TypeVar('_T')viene utilizzato per definire le classi generiche . Per la Counterclasse possiamo vedere che non può prendere argomenti nel suo inizializzatore, ottenere un singolo Mappingda qualsiasi tipo a un int o prendere uno Iterabledi qualsiasi tipo.


Avviso : una cosa che ho dimenticato di menzionare è che il typingmodulo è stato introdotto a titolo provvisorio . Da PEP 411 :

Un pacchetto provvisorio può avere la sua API modificata prima di "laurearsi" in uno stato "stabile". Da un lato, questo stato offre al pacchetto i vantaggi di essere formalmente parte della distribuzione Python. D'altra parte, il team di sviluppo principale afferma esplicitamente che non vengono fatte promesse in merito alla stabilità dell'API del pacchetto, che potrebbe cambiare per la prossima versione. Sebbene sia considerato un risultato improbabile, tali pacchetti possono anche essere rimossi dalla libreria standard senza un periodo di ammortamento se le preoccupazioni relative alla loro API o manutenzione si dimostrano fondate.

Quindi prendi le cose qui con un pizzico di sale; Dubito che verrà rimosso o modificato in modi significativi, ma non si può mai sapere.


** Un altro argomento del tutto, ma valido nell'ambito dei suggerimenti di tipo PEP 526: La sintassi per le annotazioni delle variabili è uno sforzo per sostituire i # typecommenti introducendo una nuova sintassi che consente agli utenti di annotare il tipo di variabili in semplici varname: typeistruzioni.

Vedi Cosa sono le annotazioni variabili in Python 3.6? , come accennato in precedenza, per una piccola introduzione su questi.


3
"A causa della natura altamente dinamica di Python, dedurre o verificare il tipo di oggetto utilizzato è particolarmente difficile." Ti riferisci al controllo statico, giusto?
bsam,

53

Aggiungendo alla risposta elaborata di Jim:

Controlla il typingmodulo : questo modulo supporta i suggerimenti sul tipo come specificato da PEP 484 .

Ad esempio, la seguente funzione accetta e restituisce valori di tipo stred è annotata come segue:

def greeting(name: str) -> str:
    return 'Hello ' + name

Il typingmodulo supporta anche:

  1. Digitare aliasing .
  2. Digitare hinting per le funzioni di callback .
  3. Generici : le classi di base astratte sono state estese per supportare la sottoscrizione per indicare i tipi previsti per gli elementi contenitore.
  4. Tipi generici definiti dall'utente: una classe definita dall'utente può essere definita come una classe generica.
  5. Qualsiasi tipo : ogni tipo è un sottotipo di Qualsiasi.

26

La nuova versione di PyCharm 5 supporta i suggerimenti sul tipo. Nel loro post sul blog (vedi i suggerimenti sul tipo di Python 3.5 in PyCharm 5 ) offrono una grande spiegazione di quali suggerimenti di tipo sono e non sono insieme a diversi esempi e illustrazioni su come usarli nel tuo codice.

Inoltre, è supportato in Python 2.7, come spiegato in questo commento :

PyCharm supporta il modulo di digitazione di PyPI per Python 2.7, Python 3.2-3.4. Per 2.7 devi inserire i suggerimenti sul tipo nei file * .pyi stub poiché le annotazioni delle funzioni sono state aggiunte in Python 3.0 .


0

I suggerimenti sul tipo sono un'aggiunta recente a un linguaggio dinamico in cui per decenni la gente ha giurato convenzioni di denominazione semplici come ungherese (etichetta oggetto con prima lettera b = booliana, c = carattere, d = dizionario, i = intero, l = elenco, n = numerico , s = string, t = tuple) non erano necessari, troppo ingombranti, ma ora hanno deciso che, oh aspetta ... è troppo disturbo usare il linguaggio (type ()) per riconoscere gli oggetti e i nostri fantastici IDE bisogno di aiuto per fare qualcosa di così complicato e che i valori degli oggetti assegnati in modo dinamico li rendono comunque completamente inutili, mentre una semplice convenzione di denominazione avrebbe potuto risolvere tutto, per qualsiasi sviluppatore, a colpo d'occhio.


Ad essere sinceri, questo suona più come uno sfogo che come una risposta.
Dimitris Fasarakis Hilliard,

-1

I suggerimenti sul tipo sono per manutenibilità e non vengono interpretati da Python. Nel codice seguente, la riga def add(self, ic:int)non provoca un errore fino alla return...riga successiva :

class C1:
    def __init__(self):
        self.idn = 1
    def add(self, ic: int):
        return self.idn + ic
    
c1 = C1()
c1.add(2)

c1.add(c1)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 5, in add
TypeError: unsupported operand type(s) for +: 'int' and 'C1'
 
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.