Il percorso per il typechecking di 4 milioni di righe di codice Python. Parte 1

Oggi portiamo alla vostra attenzione la prima parte della traduzione del materiale su come Dropbox si occupa del controllo di tipo del codice Python.

Il percorso per il typechecking di 4 milioni di righe di codice Python. Parte 1

Dropbox scrive molto in Python. È un linguaggio che utilizziamo in modo estremamente diffuso, sia per i servizi di back-end che per le applicazioni client desktop. Usiamo molto anche Go, TypeScript e Rust, ma Python è il nostro linguaggio principale. Considerando la nostra scala, e stiamo parlando di milioni di righe di codice Python, si è scoperto che la digitazione dinamica di tale codice ne ha complicato inutilmente la comprensione e ha iniziato a influire seriamente sulla produttività del lavoro. Per mitigare questo problema, abbiamo iniziato a trasferire gradualmente il nostro codice al controllo del tipo statico utilizzando mypy. Questo è probabilmente il sistema di controllo del tipo 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 del tipo statico nel codice Python su questa scala. Mypy è utilizzato in migliaia di progetti in questi giorni. Questo strumento innumerevoli volte, come si suol dire, "testato in battaglia". Abbiamo fatto molta strada per arrivare dove siamo ora. Lungo la strada, ci sono state molte imprese infruttuose ed esperimenti falliti. Questo post copre la storia del controllo del tipo statico in Python, dai suoi inizi rocciosi come parte del mio progetto di ricerca, fino ai giorni nostri, quando il controllo del tipo e il suggerimento sul tipo sono diventati un luogo comune per innumerevoli 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 del tipo?

Se hai mai usato Python tipizzato dinamicamente, potresti avere un po 'di confusione sul motivo per cui ultimamente c'è stato un tale clamore intorno alla tipizzazione statica e mypy. O forse ti piace Python proprio per la sua digitazione dinamica e ciò che sta accadendo ti sconvolge semplicemente. La chiave del valore della tipizzazione statica è la scala delle soluzioni: più grande è il tuo progetto, più ti inclini alla tipizzazione statica e, alla fine, più ne hai davvero bisogno.

Supponiamo che un certo progetto abbia raggiunto le dimensioni di decine di migliaia di righe e si sia scoperto che diversi programmatori ci stanno lavorando. Guardando un progetto simile, in base alla nostra esperienza, possiamo dire che la comprensione del suo codice sarà la chiave per mantenere produttivi gli sviluppatori. Senza annotazioni di tipo, può essere difficile capire, ad esempio, quali argomenti passare a una funzione o quali tipi può restituire una funzione. Di seguito sono riportate domande tipiche a cui spesso è difficile rispondere senza utilizzare le annotazioni di tipo:

  • Questa funzione può restituire None?
  • Quale dovrebbe essere questo argomento? items?
  • Qual è il tipo di attributo id: int è, str, o forse un tipo personalizzato?
  • Questo argomento dovrebbe essere un elenco? È possibile passargli una tupla?

Se guardi il seguente frammento di codice con annotazioni di tipo e provi a rispondere a domande simili, si scopre che questo è il compito più semplice:

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

  • read_metadata non ritorna None, poiché il tipo restituito non lo è Optional[…].
  • argomento items è una sequenza di linee. Non può essere iterato in modo casuale.
  • Attributo id è una stringa di byte.

In un mondo ideale, ci si aspetterebbe che tutte queste sottigliezze siano descritte nella documentazione integrata (docstring). Ma l'esperienza fornisce molti esempi del fatto che tale documentazione spesso non viene osservata nel codice con cui devi lavorare. Anche se tale documentazione è presente nel codice, non si può contare sulla sua assoluta correttezza. Questa documentazione può essere vaga, imprecisa e aperta a fraintendimenti. In grandi team o grandi progetti, questo problema può diventare estremamente acuto.

Mentre Python eccelle nelle fasi iniziali o intermedie dei progetti, a un certo punto i progetti e le aziende di successo che utilizzano Python potrebbero dover affrontare la domanda vitale: "Dovremmo riscrivere tutto in un linguaggio tipizzato staticamente?".

I sistemi di controllo del tipo come mypy risolvono il problema di cui sopra fornendo allo sviluppatore un linguaggio formale per descrivere i tipi e controllando che le dichiarazioni di tipo corrispondano all'implementazione del programma (e, facoltativamente, controllando la loro esistenza). In generale, possiamo dire che questi sistemi mettono a nostra disposizione qualcosa come una documentazione attentamente controllata.

L'uso di tali sistemi ha altri vantaggi e sono già del tutto non banali:

  • Il sistema di controllo del tipo può rilevare alcuni piccoli (e non così piccoli) errori. Un esempio tipico è quando dimenticano di elaborare un valore None o qualche altra condizione speciale.
  • Il refactoring del codice è notevolmente semplificato perché il sistema di controllo del tipo è spesso molto preciso su quale codice deve essere modificato. Allo stesso tempo, non dobbiamo sperare in una copertura del codice al 100% con i test, che, in ogni caso, di solito non è fattibile. Non è necessario approfondire la traccia dello stack per scoprire la causa del problema.
  • Anche su progetti di grandi dimensioni, mypy può spesso eseguire il controllo completo del tipo in una frazione di secondo. E l'esecuzione dei test richiede solitamente decine di secondi o addirittura minuti. Il sistema di verifica del tipo fornisce al programmatore un feedback istantaneo e gli consente di svolgere il proprio lavoro più rapidamente. Non ha più bisogno di scrivere unit test fragili e difficili da mantenere che sostituiscono entità reali con mock e patch solo per ottenere più velocemente i risultati dei test del codice.

Gli IDE e gli editor come PyCharm o Visual Studio Code utilizzano la potenza delle annotazioni di tipo per fornire agli sviluppatori il completamento del codice, l'evidenziazione degli errori e il supporto per i costrutti linguistici comunemente usati. E questi sono solo alcuni dei vantaggi della digitazione. Per alcuni programmatori, tutto questo è l'argomento principale a favore della digitazione. Questo è qualcosa che beneficia immediatamente dopo l'implementazione. Questo caso d'uso per i tipi non richiede un sistema di controllo del tipo separato come mypy, anche se va notato che mypy aiuta a mantenere le annotazioni del tipo coerenti con il codice.

Sfondo di mypy

La storia di mypy è iniziata nel Regno Unito, a Cambridge, qualche anno prima che entrassi in Dropbox. Sono stato coinvolto, come parte della mia ricerca di dottorato, nell'unificazione di linguaggi tipizzati staticamente e dinamici. Sono stato ispirato da un articolo sulla digitazione incrementale di Jeremy Siek e Walid Taha e dal progetto Typed Racket. Ho cercato di trovare modi per utilizzare lo stesso linguaggio di programmazione per vari progetti, da piccoli script a basi di codice composte da molti milioni di righe. Allo stesso tempo, volevo assicurarmi che in un progetto di qualsiasi portata non si dovessero scendere a compromessi troppo grandi. Una parte importante di tutto ciò è stata l'idea di passare gradualmente da un progetto prototipo non tipizzato a un prodotto finito tipizzato staticamente ampiamente testato. In questi giorni, queste idee sono ampiamente date per scontate, ma nel 2010 era un problema che veniva ancora esplorato attivamente.

Il mio lavoro originale nel controllo del tipo non era rivolto a Python. Invece, ho usato un piccolo linguaggio "casalingo". ora. Ecco un esempio che vi farà capire di cosa stiamo parlando (qui le annotazioni di tipo sono facoltative):

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

L'utilizzo di una lingua madre semplificata è un approccio comune utilizzato nella ricerca scientifica. È così, anche perché ti consente di condurre rapidamente esperimenti, e anche perché ciò che non ha nulla a che fare con la ricerca può essere facilmente ignorato. I linguaggi di programmazione del mondo reale tendono ad essere fenomeni su larga scala con implementazioni complesse, e questo rallenta la sperimentazione. Tuttavia, qualsiasi risultato basato su un linguaggio semplificato appare un po' sospetto, poiché nell'ottenere questi risultati il ​​ricercatore potrebbe aver sacrificato considerazioni importanti per l'uso pratico delle lingue.

Il mio correttore di caratteri per Alore sembrava molto promettente, ma volevo provarlo sperimentando con codice reale, che, si potrebbe dire, non era scritto in Alore. Fortunatamente per me, il linguaggio Alore era in gran parte basato sulle stesse idee di Python. È stato abbastanza facile cambiare il typechecker in modo che potesse funzionare con la sintassi e la semantica di Python. Questo ci ha permesso di provare il controllo del tipo nel codice Python open source. Inoltre, ho scritto un transpiler per convertire il codice scritto in Alore in codice Python e l'ho usato per tradurre il mio codice typechecker. Ora avevo un sistema di verifica del tipo scritto in Python che supportava un sottoinsieme di Python, una specie di quel linguaggio! (Alcune decisioni architettoniche che avevano senso per Alore erano poco adatte per Python, e questo è ancora evidente in alcune parti del codice mypy.)

In effetti, il linguaggio supportato dal mio sistema di tipi non poteva essere chiamato Python a questo punto: era una variante di Python a causa di alcune limitazioni della sintassi dell'annotazione di tipo 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 all'epoca era quella di utilizzare le annotazioni di tipo per migliorare le prestazioni compilando questo tipo di Python in C, o forse in bytecode JVM. Sono arrivato alla fase di scrittura di un prototipo di compilatore, ma ho abbandonato questa idea, poiché il controllo del tipo stesso sembrava piuttosto utile.

Ho finito per presentare il mio progetto al PyCon 2013 a Santa Clara. Ne ho parlato anche con Guido van Rossum, il benevolo dittatore a vita di Python. Mi ha convinto ad abbandonare la mia sintassi e ad attenermi alla sintassi standard di Python 3. Python 3 supporta le annotazioni delle funzioni, quindi il mio esempio potrebbe essere riscritto come mostrato di seguito, risultando in 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 (prima di tutto voglio sottolineare che ho inventato la mia sintassi proprio per questo motivo). In particolare, Python 3.3, all'epoca la versione più recente del linguaggio, non supportava le annotazioni variabili. Ho discusso con Guido via e-mail varie possibilità per la progettazione sintattica di tali annotazioni. Abbiamo deciso di utilizzare i commenti di tipo per le variabili. Questo serviva allo scopo previsto, ma era alquanto ingombrante (Python 3.6 ci ha fornito una sintassi migliore):

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

Anche i commenti di tipo sono stati utili per supportare Python 2, che non ha il supporto integrato per le annotazioni di tipo:

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 molta importanza: i vantaggi della tipizzazione statica hanno fatto sì che gli utenti si dimenticassero presto della sintassi tutt'altro che perfetta. Poiché non sono stati utilizzati costrutti sintattici speciali nel codice Python verificato dal tipo, gli strumenti Python esistenti ei processi di elaborazione del codice hanno continuato a funzionare normalmente, rendendo molto più facile per gli sviluppatori apprendere il nuovo strumento.

Guido mi ha anche convinto ad unirmi a Dropbox dopo aver completato la mia tesi di laurea. È qui che inizia la parte più interessante della storia di mypy.

To be continued ...

Cari lettori! Se usi Python, parlaci della portata dei progetti che sviluppi in questo linguaggio.

Il percorso per il typechecking di 4 milioni di righe di codice Python. Parte 1
Il percorso per il typechecking di 4 milioni di righe di codice Python. Parte 1

Fonte: habr.com

Aggiungi un commento