Il percorso per la verifica dei tipi in 4 milioni di righe di codice Python. Parte 1

Oggi vi presentiamo la prima parte della traduzione del materiale su come in Dropbox gestiscono il controllo dei tipi nel codice Python.

Il percorso per la verifica dei tipi in 4 milioni di righe di codice Python. Parte 1

In Dropbox scriviamo molto in Python. Questo è il linguaggio che utilizziamo in modo estremamente ampio, sia per i servizi di backend che per le applicazioni client desktop. Utilizziamo anche Go, TypeScript e Rust in grandi quantità, ma Python è il nostro linguaggio principale. Considerando le nostre dimensioni, parliamo di milioni di righe di codice Python, ed è emerso che la tipizzazione dinamica di questo codice aveva reso la sua comprensione ingiustificatamente complessa, influenzando seriamente la produttività. Per mitigare questo problema, abbiamo avviato un processo graduale di conversione del nostro codice verso un controllo statico dei tipi utilizzando mypy. Probabilmente, è il sistema di verifica dei tipi autonomo più popolare per Python. Mypy è un progetto open source, i suoi principali sviluppatori lavorano in Dropbox.

Dropbox è stata una delle prime aziende a implementare il controllo statico dei tipi nel codice Python su scala così vasta. Oggi mypy è utilizzato in migliaia di progetti. Questo strumento è stato testato un numero incalcolabile di volte, per così dire, "in battaglia". Per arrivare dove siamo ora, abbiamo dovuto percorrere un lungo cammino. Lungo questo percorso ci sono stati numerosi tentativi falliti e esperimenti andati male. Questo materiale racconta la storia del controllo statico dei tipi in Python — dall'inizio difficile, che faceva parte del mio progetto di ricerca, fino ad oggi, quando i controlli di tipo e le suggerimenti sui tipi sono diventati familiari a un numero innumerevole di sviluppatori che scrivono in Python. Questi meccanismi sono ora supportati da molti strumenti — come IDE e analizzatori di codice.

Leggi la seconda parte

Perché è necessario il controllo dei tipi?

Se hai mai utilizzato Python con tipizzazione dinamica, potresti non comprendere appieno il recente clamore attorno alla tipizzazione statica e a mypy. Forse ami Python proprio per la sua tipizzazione dinamica, e tutto ciò ti lascia perplesso. La chiave del valore della tipizzazione statica è la scala delle soluzioni: più grande è il tuo progetto, più ti inclini verso la tipizzazione statica e, alla fine, più ne hai veramente bisogno.

Immagina che un progetto abbia raggiunto dimensioni di decine di migliaia di righe e che vi stiano lavorando diversi programmatori. Analizzando un progetto simile, possiamo affermare, basandoci sulla nostra esperienza, che comprendere il codice sarà fondamentale per mantenere la produttività degli sviluppatori. Senza annotazioni di tipo, può risultare complicato determinare, ad esempio, quali argomenti devono essere passati a una funzione o quali tipi di valori può restituire una determinata funzione. Ecco alcune domande tipiche a cui spesso non è facile rispondere senza l'uso delle annotazioni di tipo:

  • Questa funzione può restituire Nessuno?
  • Qual è il tipo di argomento items?
  • Qual è il tipo dell'attributo id: -int è questo, str, o forse un tipo personalizzato?
  • Deve questo argomento essere una lista? Può accettare una tupla?

Se guardiamo il seguente frammento di codice, completo di annotazioni sui tipi, e cerchiamo di rispondere a domande simili, scopriremo che è un compito piuttosto semplice:

class Resource:
    id: bytes
    ...
    def read_metadata(self, 
                      items: Sequence[str]) -> Dict[str, MetadataItem]:
        ...

  • read_metadata non restituisce Nessuno, poiché il tipo restituito non è Optional[…].
  • Argomento items — è una sequenza di stringhe. Non può essere iterata in ordine arbitrario.
  • L'attributo id — è una stringa di byte.

In un mondo ideale, ci si aspetterebbe che tutte queste sottigliezze siano descritte nella documentazione integrata (docstring). Tuttavia, l'esperienza offre moltissimi esempi in cui tale documentazione manca nel codice con cui si lavora. Anche quando la documentazione è presente, non si può fare affidamento sulla sua assoluta correttezza. Questa documentazione potrebbe essere poco chiara, imprecisa, lasciando ampio spazio a possibili fraintendimenti. Nei grandi team o in progetti estesi, questo problema può diventare estremamente critico.

Sebbene Python si dimostri eccellente nelle fasi iniziali o intermedie dei progetti, a un certo punto progetti e aziende di successo che utilizzano Python possono affrontare una questione vitale: «Dobbiamo riscrivere tutto in un linguaggio a tipizzazione statica?».

I sistemi di controllo dei tipi come mypy affrontano il problema sopra menzionato grazie alla loro capacità di fornire agli sviluppatori un linguaggio formale per descrivere i tipi e di verificare che le descrizioni dei tipi siano conformi alle implementazioni dei programmi (e, facoltativamente, di verificarne l'esistenza). In generale, si può dire che questi sistemi offrono qualcosa simile a una documentazione accuratamente verificata.

L'uso di tali sistemi presenta anche altri vantaggi che non sono affatto trascurabili:

  • Il sistema di verifica dei tipi può rilevare alcuni errori minori (ma anche non trascurabili). Un esempio tipico è quando si dimentica di gestire un valore Nessuno o una qualche altra condizione particolare.
  • Il refactoring del codice diventa significativamente più semplice, poiché il sistema di verifica dei tipi spesso indica con grande precisione quale codice deve essere modificato. Non dobbiamo affidarci a una copertura del 100% del codice con test, il che, in ogni caso, è solitamente irrealizzabile. Non è necessario esaminare in profondità i rapporti sui trace stack per scoprire la causa di un malfunzionamento.
  • Anche nei progetti di grandi dimensioni, mypy può spesso completare un controllo di tipo in frazioni di secondo. L'esecuzione dei test, invece, di solito richiede decine di secondi o addirittura minuti. Il sistema di controllo dei tipi fornisce al programmatore un feedback immediato, permettendogli di lavorare più velocemente. Non è più necessario scrivere test modulari fragili e difficili da mantenere, che sostituiscono le entità reali con oggetti mock e patch solo per ottenere risultati più rapidamente.

IDE e editor, come PyCharm o Visual Studio Code, utilizzano le funzionalità delle annotazioni di tipo per offrire agli sviluppatori funzionalità di completamento automatico del codice, evidenziazione degli errori e supporto per le costrizioni linguistiche più comuni. E questi sono solo alcuni dei vantaggi offerti dalla tipizzazione. Per alcuni programmatori, tutto ciò rappresenta l'argomento principale a favore della tipizzazione. Questo è qualcosa che porta benefici immediati una volta implementato nel lavoro. Questo utilizzo dei tipi non richiede l'applicazione di un sistema di controllo dei tipi separato, come mypy, anche se va notato che mypy aiuta a mantenere la corrispondenza tra annotazioni di tipo e codice.

Storia di mypy

La storia di mypy è iniziata nel Regno Unito, a Cambridge, alcuni anni prima che mi unissi a Dropbox. Durante il mio dottorato, mi occupavo della questione dell'unificazione dei linguaggi a tipizzazione statica e dinamica. Sono stato ispirato da un articolo sulla tipizzazione graduale di Jeremy Siek e Walid Taha, oltre che dal progetto Typed Racket. Cercavo modi per utilizzare lo stesso linguaggio di programmazione per vari progetti — da piccoli script a basi di codice composte da milioni di righe. Desideravo che per progetti di qualsiasi dimensione non fosse necessario scendere a compromessi eccessivi. Un aspetto importante di tutto questo era l'idea di un passaggio graduale da un prototipo non tipizzato a un prodotto finito staticamente tipizzato e ampiamente testato. Oggi queste idee sono, in gran parte, scontate, ma nel 2010 rappresentavano una questione ancora attivamente esplorata.

Il mio lavoro iniziale nel campo della verifica dei tipi non era focalizzato su Python. Al suo posto, utilizzavo un piccolo linguaggio "artigianale". AloreEcco un esempio che ti aiuterà a capire di cosa si tratta (le annotazioni sui tipi qui non sono obbligatorie):

def Fib(n as Int) as Int
  if n <= 1
    return n
  else
    return Fib(n - 1) + Fib(n - 2)
  end
end

L'uso di un linguaggio semplificato sviluppato in proprio è un approccio comune nelle ricerche scientifiche. Questo è in gran parte dovuto al fatto che permette di condurre esperimenti rapidamente e che si possono ignorare senza troppe difficoltà aspetti non rilevanti per la ricerca. I linguaggi di programmazione realmente utilizzati sono generalmente fenomeni di vasta portata con implementazioni complesse, il che rallenta questi esperimenti. Tuttavia, qualsiasi risultato basato su un linguaggio semplificato appare un po' sospetto, poiché, nella sua ricerca di tali risultati, il ricercatore potrebbe aver sacrificato considerazioni importanti per l'uso pratico dei linguaggi.

Il mio strumento di verifica dei tipi per Alore sembrava molto promettente, ma volevo testarlo eseguendo esperimenti con codice reale, che, si può dire, non era scritto in Alore. Fortunatamente, il linguaggio Alore si basava in gran parte sulle stesse idee del Python. È stato piuttosto semplice adattare lo strumento di verifica dei tipi affinché funzionasse con la sintassi e la semantica di Python. Questo ha permesso di provare a eseguire la verifica dei tipi su codice Python open source. Inoltre, ho scritto un transpiler per convertire il codice scritto in Alore in codice Python e l'ho utilizzato per tradurre il codice del mio strumento di verifica dei tipi. Ora avevo un sistema per la verifica dei tipi, scritto in Python, che supportava un sottoinsieme di Python, una sua particolare versione! (Alcune decisioni architettoniche che avevano senso per Alore si adattavano male a Python, il che è ancora evidente in alcune parti della base di codice di mypy.)

In realtà, il linguaggio supportato dal mio sistema di tipi non poteva essere considerato Python in quel momento: era una variante di Python a causa di alcune limitazioni nella sintassi delle annotazioni di tipo di Python 3.

Sembrava una miscela di Java e Python:

int fib(int n):
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

Una delle mie idee a quel tempo era di usare le annotazioni di tipo per migliorare le prestazioni compilando questa variante di Python in C, o forse in bytecode JVM. Sono arrivato a scrivere un prototipo del compilatore, ma ho abbandonato l'idea, poiché la verifica dei tipi sembrava già abbastanza utile.

Alla fine, ho presentato il mio progetto alla conferenza PyCon 2013 a Santa Clara. Ho anche parlato di questo con Guido van Rossum, il generoso dittatore a vita di Python. Mi ha convinto ad abbandonare la mia sintassi e a mantenere la sintassi standard di Python 3. Python 3 supporta le annotazioni delle funzioni, quindi il mio esempio poteva essere riscritto come mostrato di seguito, producendo un normale programma Python:

def fib(n: int) -> int:
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

Ho dovuto scendere a compromessi (voglio sottolineare che ho inventato la mia sintassi per questo motivo). In particolare, Python 3.3, l'ultima versione del linguaggio all'epoca, non supportava le annotazioni delle variabili. Ho discusso con Guido via e-mail varie opzioni per la sintassi di tali annotazioni. Abbiamo deciso di utilizzare commenti con i tipi per le variabili. Questo ci ha permesso di raggiungere l'obiettivo, ma sembrava un po' ingombrante (Python 3.6 ci ha fornito una sintassi più gradevole):

products = []  # type: List[str]  # Eww

I commenti con i tipi, inoltre, sono stati utili per supportare Python 2, che non ha il supporto nativo per le annotazioni dei tipi:

f fib(n):
    # type: (int) -> int
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

Si è scoperto che questi (e altri) compromessi non avevano realmente importanza: i vantaggi della tipizzazione statica hanno fatto sì che gli utenti dimenticassero rapidamente la sintassi non perfetta. Poiché ora il codice Python, in cui venivano controllati i tipi, non richiedeva costruzioni sintattiche particolari, gli strumenti e i processi esistenti per la gestione del codice in Python hanno continuato a funzionare normalmente, rendendo molto più facile per gli sviluppatori imparare a utilizzare il nuovo strumento.

Guido, inoltre, mi ha convinto a unirmi a Dropbox dopo che ho difeso la mia tesi. Qui inizia la parte più interessante della storia di mypy.

Continua...

Gentili lettori! Se utilizzi Python, ti invitiamo a raccontarci di che tipo di progetti stai sviluppando in questo linguaggio.

Il percorso per la verifica dei tipi in 4 milioni di righe di codice Python. Parte 1
Il percorso per la verifica dei tipi in 4 milioni di righe di codice Python. Parte 1

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster