Astăzi vă oferim prima parte a traducerii materialului despre cum Dropbox gestionează tipurile de cod Python.
La Dropbox se scrie mult în Python. Acesta este un limbaj pe care îl folosim extrem de pe larg atât pentru servicii backend, cât și pentru aplicații client pe desktop. De asemenea, folosim în cantități mari Go, TypeScript și Rust, dar Python este principalul nostru limbaj. Având în vedere dimensiunile noastre, vorbind despre milioane de linii de cod Python, s-a dovedit că tipizarea dinamică a acestui cod complică nejustificat înțelegerea și începe să afecteze serios productivitatea muncii. Pentru a atenua această problemă, am început un proces treptat de migrare a codului nostru la verificarea statică a tipurilor folosind mypy. Aceasta este probabil cea mai populară soluție de verificare a tipurilor pentru Python. Mypy este un proiect open-source, iar principalii săi dezvoltatori lucrează la Dropbox.
Dropbox a fost una dintre primele companii care a implementat verificarea statică a tipurilor în codul Python la o asemenea scară. În prezent, mypy este folosit în mii de proiecte. Acest instrument a fost testat de nenumărate ori în situații reale. Pentru a ajunge la nivelul la care ne aflăm acum, a fost nevoie să parcurgem un drum lung. Pe acest parcurs, au existat numeroase inițiative nereușite și experimente eșuate. Acest material povestește despre istoria verificării statice a tipurilor în Python — de la începuturile ei dificile, care au fost parte din proiectul meu de cercetare științifică, până în prezent, când verificările de tip și sugestiile de tip au devenit obișnuite pentru nenumărați dezvoltatori care scriu în Python. Aceste mecanisme sunt acum susținute de numeroase instrumente — precum IDE-uri și analizoare de cod.
→
De ce este necesară verificarea tipurilor?
Dacă ați folosit vreodată Python cu tipare dinamice, s-ar putea să aveți unele neclarități cu privire la de ce s-a iscat atâta vâlvă în jurul tipării statice și mypy în ultima vreme. Poate că vă place Python tocmai din cauza tipării sale dinamice și ceea ce se întâmplă vă deranjează. Cheia valorii tipării statice este dimensiunea soluțiilor: cu cât proiectul dumneavoastră este mai mare, cu atât sunteți mai înclinat spre tiparea statică și, în cele din urmă, cu atât aveți mai multă nevoie de aceasta.
Să presupunem că un anumit proiect a ajuns la dimensiuni de zeci de mii de linii și s-a dovedit că este gestionat de mai mulți programatori. Privind un astfel de proiect, ne putem baza pe experiența noastră pentru a afirma că înțelegerea codului său va fi cheia menținerii productivității dezvoltatorilor. Fără adnotări de tipuri, este adesea dificil să aflăm, de exemplu, ce argumente trebuie transmise unei funcții sau ce tipuri de valori poate returna o anumită funcție. Iată întrebările tipice la care adesea este greu de găsit un răspuns fără a folosi adnotări de tipuri:
- Poate această funcție să returneze
None? - Ce ar trebui să fie acest argument
items? - Care este tipul atributului
id:inteste acesta,str, sau, poate, un tip personalizat? - Trebuie să fie acest argument o listă? Se poate transmite un tuplu în el?
Dacă privim următorul fragment de cod, dotat cu adnotări de tipuri, și încercăm să răspundem la astfel de întrebări, vom descoperi că aceasta este o sarcină simplă:
class Resource:
id: bytes
...
def read_metadata(self,
items: Sequence[str]) -> Dict[str, MetadataItem]:
...read_metadatanu returneazăNone, deoarece tipul returnat nu esteOptional[…].- Argumentul
itemseste o secvență de șiruri. Nu poate fi iterată în mod aleator. - Atributul
ideste un șir de octeți.
Într-o lume ideală, ne-am aștepta ca toate aceste subtilități să fie descrise în documentația încorporată (docstring). Totuși, experiența oferă numeroase exemple că o astfel de documentație în codul cu care lucrăm este adesea absentă. Chiar și atunci când documentația există, nu putem conta pe corectitudinea ei absolută. Această documentație poate fi neclară, inexactă, lăsând multe posibilități pentru interpretări greșite. În echipe mari sau în proiecte mari, această problemă poate deveni extrem de acută.
Deși Python se descurcă excelent în etapele timpurii sau intermediare ale proiectelor, la un moment dat, proiectele și companiile de succes care folosesc Python pot înfrunta o întrebare crucială: „Trebuie să rescriem totul într-o limbă cu tipuri statice?”.
Sistemele de verificare a tipurilor, precum mypy, rezolvă problema menționată anterior prin faptul că oferă dezvoltatorilor un limbaj formal pentru descrierea tipurilor și că verifică faptul că descrierile tipurilor corespund implementărilor programelor (și, fără a fi obligatoriu, verifică dacă acestea există). În general, se poate spune că aceste sisteme ne oferă ceva similar cu documentația bine verificată.
Există și alte avantaje de a utiliza astfel de sisteme, iar acestea nu sunt deloc neglijabile:
- Sistemul de verificare a tipurilor poate detecta unele erori minore (dar și erori mai serioase). Un exemplu tipic este atunci când se uită să se gestioneze o valoare
Nonesau o altă condiție specială. - Refactorizarea codului devine semnificativ mai simplă, deoarece sistemul de verificare a tipurilor indică adesea foarte precis ce cod trebuie să fie modificat. În acest fel, nu trebuie să ne bazăm pe o acoperire de 100% a codului cu teste, ceea ce, în orice caz, este de obicei imposibil. Nu trebuie să studiem profunzimea rapoartelor de urmărire a stivei pentru a afla cauza erorilor.
- Chiar și în proiecte mari, mypy poate adesea să efectueze o verificare completă a tipurilor în fracțiuni de secundă. Executarea testelor durează în mod obișnuit zeci de secunde sau chiar minute. Sistemul de verificare a tipurilor oferă programatorului feedback instantaneu și îi permite să își facă treaba mai repede. Nu mai trebuie să scrie teste modulare fragile și greu de întreținut, care substituie entitățile reale cu mock-uri și patch-uri doar pentru a obține rezultate mai repede.
IDE-urile și editorii, precum PyCharm sau Visual Studio Code, folosesc capacitățile de anotare a tipurilor pentru a oferi dezvoltatorilor funcționalități de completare automată a codului, evidențierea erorilor și suport pentru construcțiile de limbaj frecvent utilizate. Și acestea sunt doar câteva dintre avantajele pe care le oferă tipizarea. Pentru unii programatori, toate acestea sunt principalul argument în favoarea tipizării. Este ceva ce aduce beneficii imediat după implementarea în muncă. Această utilizare a tipurilor nu necesită aplicarea unui sistem separat de verificare a tipurilor, cum ar fi mypy, deși trebuie menționat că mypy ajută la menținerea coerenței între anotările tipurilor și cod.
Contextul mypy
Povestea mypy a început în Marea Britanie, la Cambridge, cu câțiva ani înainte să mă alătur Dropbox. Am studiat, în cadrul unei cercetări de doctorat, unificarea limbajelor tipizate static și dinamic. M-a inspirat un articol despre tipizarea treptată de Jeremy Siek și Valida Taha, precum și proiectul Typed Racket. Am încercat să găsesc modalități de a folosi același limbaj de programare pentru diferite proiecte — de la scripturi mici la baze de cod ce constau din milioane de linii. În același timp, mi-aș fi dorit ca în proiectul de orice dimensiune să nu fie necesar să fac compromisuri excesive. O parte importantă din toate acestea era ideea unei tranziții treptate de la un prototip netipizat al proiectului la un produs final complet testat și tipizat static. În zilele noastre, aceste idei sunt, în mare măsură, considerate de la sine înțelese, dar în 2010 aceasta era o problemă care continua să fie activ cercetată.
Lucrarea mea inițială în domeniul verificării tipurilor nu a fost axată pe Python. În schimb, am folosit un mic limbaj 'artizanal' Iată un exemplu care vă va ajuta să înțelegeți despre ce este vorba (annotările de tipuri sunt opționale aici):
def Fib(n as Int) as Int
if n <= 1
return n
else
return Fib(n - 1) + Fib(n - 2)
end
endUtilizarea unui limbaj simplificat dezvoltat în mod propriu este o abordare obișnuită în cercetarea științifică. Acest lucru se datorează în mare parte faptului că permite desfășurarea rapidă a experimentelor, precum și pentru că ceea ce nu are legătură cu cercetarea poate fi ignorat fără nicio problemă. Limbajele de programare utilizate în realitate reprezintă, de obicei, fenomene extinse cu implementări complexe, ceea ce încetinește desfășurarea experimentelor. Cu toate acestea, rezultatele bazate pe un limbaj simplificat par puțin suspecte, deoarece, în obținerea acestor rezultate, cercetătorul ar fi putut să sacrifice preocupări importante pentru utilizarea practică a limbajelor.
Instrumentul meu de verificare a tipurilor pentru Alore părea foarte promițător, dar voiam să-l testez prin experimentarea cu cod real, care, așa cum s-ar putea spune, nu a fost scris în Alore. Din fericire, limba Alore s-a bazat în mare măsură pe aceleași idei ca Python. A fost suficient de simplu să refac instrumentul de verificare a tipurilor astfel încât să poată funcționa cu sintaxa și semantica Python. Acest lucru a permis testarea verificării tipurilor în codul Python open-source. În plus, am scris un transpiler pentru a transforma codul scris în Alore în cod Python și l-am folosit pentru a traduce codul instrumentului meu de verificare a tipurilor. Acum aveam un sistem de verificare a tipurilor, scris în Python, care susținea un subset al Python, o variantă a acestui limbaj! (Anumite decizii arhitecturale care aveau sens pentru Alore nu se potriveau bine pentru Python; acest lucru este încă evident în unele părți din baza de cod mypy.)
De fapt, limba susținută de sistemul meu de tipuri nu putea fi numită complet Python în acel moment: era o variantă Python din cauza anumitor restricții ale sintaxei anotărilor de tipuri Python 3.
Arăta ca o combinație între Java și Python:
int fib(int n):
if n <= 1:
return n
else:
return fib(n - 1) + fib(n - 2)Una dintre ideile mele de atunci a fost să folosesc adnotări de tip pentru a îmbunătăți performanța, compilând această variantă de Python în C sau, poate, în bytecode JVM. Am avansat până la scrierea unui prototip de compilator, dar am abandonat această idee, deoarece verificarea tipurilor părea deja destul de utilă.
În cele din urmă, mi-am prezentat proiectul la conferința PyCon 2013 din Santa Clara. De asemenea, am discutat despre asta cu Guido van Rossum, generosul dictator pe viață al Python. El m-a convins să renunț la propriul sintax și să respect sintaxa standard a Python 3. Python 3 suportă adnotările funcțiilor, așa că exemplul meu putea fi rescris astfel, devenind un program Python valid:
def fib(n: int) -> int:
if n <= 1:
return n
else:
return fib(n - 1) + fib(n - 2)A trebuit să facunele compromisuri (în principal, vreau să menționez că mi-am inventat propria sintax din acest motiv). În special, Python 3.3, cea mai recentă versiune a limbajului la acel moment, nu suporta adnotările variabilelor. Am discutat prin email cu Guido diverse opțiuni pentru formatarea sintactică a acestor adnotări. Am decis să folosim comentarii cu specificații de tip pentru variabile. Acest lucru permitea atingerea scopului propus, dar părea cam cumbersome (Python 3.6 ne-a oferit o sintaxă mai plăcută):
products = [] # type: List[str] # UghComentariile cu tipuri s-au dovedit utile, de asemenea, pentru suportul Python 2, care nu are suport încorporat pentru adnotările de tip:
f fib(n):
# type: (int) -> int
if n <= 1:
return n
else:
return fib(n - 1) + fib(n - 2)Se pare că aceste (și alte) compromisuri nu au contat prea mult — avantajele tipizării statice au făcut ca utilizatorii să uite curând de sintaxa nu tocmai ideală. Deoarece acum în codul Python, în care tipurile erau controlate, nu se aplicau construcții sintactice speciale, uneltele și procesele existente pentru procesarea codului Python au continuat să funcționeze normal, făcând mai ușor pentru dezvoltatori să învețe acest nou instrument.
Guido m-a convins, de asemenea, să mă alătur Dropbox după ce mi-am susținut lucrarea de licență. Aici începe partea interesantă a poveștii mypy.
Continuarea urmează...
Stimați cititori! Dacă folosiți Python, vă rugăm să ne spuneți despre proiectele de ce dimensiune dezvoltați în acest limbaj.
Sursa: habr.com
