Täna tutvustame teile esimest osa materjalist, mis käsitleb, kuidas Dropboxis tegeletakse Python-koodi tüüpide kontrollimisega.
Dropboxis kirjutatakse palju Pythonis. See on keel, mida kasutame äärmiselt laialdaselt — nii tagaplaani teenuste kui ka lauaarvuti rakenduste jaoks. Kasutame ka suurel hulgal Go, TypeScripti ja Rusti, kuid Python on meie peamine keel. Arvestades meie ulatust, millega on tegemist miljonite ridade Python-koodiga, on selgunud, et dünaamiline tüpiseerimine on selle mõistmist ebaproportsionaalselt keerukaks muutnud ja hakanud tõsiselt mõjutama tööproduktiivsust. Selle probleemi leevendamiseks hakkasime järk-järgult oma koodi üle viima staatilisele tüüpide kontrollimisele, kasutades mypy-d. See on tõenäoliselt kõige populaarsem iseseisev tüüpide kontrollimise süsteem Pythonile. Mypy on avatud lähtekoodiga projekt, mille peamised arendajad töötavad Dropboxis.
Dropbox oli üks esimesi ettevõtteid, kes rakendas staatilist tüüpi kontrolli Python-koodis sellises ulatuses. Täna kasutatakse mypy't tuhandetes projektides. See tööriist on olnud lõputult, nagu öeldakse, „lahingus testitud“. Kuni sinna, kus me oleme täna, oleme läbinud pika tee. Sellel teel oli mitmeid ebaõnnestunud algatusi ja katkestatud katseid. See materjal räägib staatilise tüübikontrolli ajaloost Pythoni keeles — alates raskest algusest, mis oli osa minu teadusprojektist, kuni tänapäevani, mil tüübi kontroll ja tüübi vihjed on saanud igapäevaseks paljudele arendajatele, kes kirjutavad Pythoni keeles. Need mehhanismid on nüüdseks mitmete tööriistade toetatud — nagu IDE-d ja koodianalüsaatorid.
→
Miks on tüübikontroll vajalik?
Kui olete kunagi kasutanud dünaamiliselt tüübitud Pythonit, võib teil olla arusaamatus, miks on viimastel aegadel nii suur hääl statilise tüpiseerimise ja mypy ümber. Võib-olla meeldib teile Python just selle dünaamilise tüpiseerimise tõttu ning see, mis toimub, häirib teid lihtsalt. Statilise tüpiseerimise väärtuse võti on lahenduste ulatus: mida suurem on teie projekt, seda enam kaldute statilise tüpiseerimise poole ja lõpuks on see teile tõeliselt vajalik.
Oletame, et mõni projekt on jõudnud kümnete tuhandete ridade suuruseks ning sellega tegeleb mitu programmeerijat. Sellise projekti puhul saame oma kogemuste põhjal öelda, et koodi arusaamine muutub arendajate tootlikkuse säilitamise võtmeelementiks. Ilma tüüpannotatsioonideta on keeruline näiteks välja selgitada, milliseid argumente tuleks funktsioonile edastada, või milliseid väärtuste tüüpe võib mõni funktsioon tagastada. Siin on tüüpilised küsimused, millele on sageli keeruline vastata, kui tüüpannotatsioone ei kasutata:
- Kas see funktsioon saab tagastada
Puudub? - Millega peaks see argument olema
esemed? - Mis on atribuudi tüüp
id:intkas see on,str, või võib-olla mingi kasutaja määratud tüüp? - Kas see argument peaks olema loend? Kas sinna võib edastada tulpade kogumi?
Kui vaadata järgmise koodilõigu tüübiannotatsioonidega ja proovida vastata sarnastele küsimustele, selgub, et see on kõige lihtsam ülesanne:
class Resource:
id: bytes
...
def read_metadata(self,
items: Sequence[str]) -> Dict[str, MetadataItem]:
...read_metadataei tagastaPuudub, kuna tagastatav tüüp ei oleOptional[…].- Argument
esemed— see on stringide jada. Seda ei saa iteratsioonis teha juhuslikus järjekorras. - Atribuut
id— see on baitide string.
Ideaalsetes tingimustes võiks oodata, et kõik sellised nüansid on kajastatud sisseehitatud dokumentatsioonis (docstring). Kuid praktika toob ette hulga näiteid, et sellist dokumentatsiooni koodis, millega tuleb töötada, sageli ei leidu. Isegi kui selline dokumentatsioon on olemas, ei saa loota selle absoluutsele õigsusele. See dokumentatsioon võib olla ebaselge, ebatäpne ja avatud vale mõistmisele. Suurtes meeskondades või suurtes projektides võib see probleem muutuda äärmiselt teravaks.
Kuigi Python toimib suurepäraselt projektide varajastes või vahepealsetes etappides, võivad teatud hetkel edukad projektid ja ettevõtted, kes kasutavad Pythoni, silmitsi seista eluliselt olulise küsimusega: „Kas peame kõik kirjutama ümber staatiliselt tüübistatavas keeles?”
Tüüpide kontrolli süsteemid, nagu mypy, lahendavad eespool mainitud probleemi, pakkudes arendajatele formaalset keelt tüüpide kirjeldamiseks ning kontrollides, et tüüpide kirjeldused vastavad programmide teostusele (ja vajadusel kontrollides ka nende olemasolu). Üldiselt võib öelda, et need süsteemid annavad meile midagi sellist nagu põhjalikult kontrollitud dokumentatsioon.
Selliste süsteemide kasutamisel on ka teisi eeliseid, mis on juba täiesti märkimisväärsed:
- Tüübi kontrollimise süsteem suudab tuvastada mõningaid väikseid (aga ka mitte eriti väikeseid) vigu. Tüüpiline näide on see, kui unustatakse töödelda väärtust
Puudubvõi mõnda muud eritingimust. - Koodi refaktoreerimine muutub oluliselt lihtsamaks, kuna tüübi kontrollimise süsteem annab sageli väga täpset tagasisidet, millist koodi tuleb muuta. Samal ajal ei pea me lootma 100% katvusele testidega, mis igal juhul on tavaliselt saavutatav. Me ei pea uurima virna jälgimisaruande sügavusi, et välja selgitada tõrke põhjus.
- Isegi suurte projektide puhul suudab mypy tihti teostada täielikku tüüpide kontrolli murdosa sekundiga. Ja testide käivitamine võtab tavaliselt kümneid sekundeid või isegi minuteid. Tüüpide kontrollimise süsteem annab programmeerijale kohese tagasiside, mis aitab tal oma tööd kiiremini teha. Talle ei pea enam kirjutama habras ja keerukaid hooldada modulaarseid teste, mis asendavad reaalsed üksused mokkade ja patchidega ainult selleks, et kiiremini saada koodi katsetamise tulemusi.
IDE-d ja redigeerijad nagu PyCharm või Visual Studio Code kasutavad tüpiseerimise annotatsioonide võimalusi, et pakkuda arendajatele koodi automaatset täiendamist, errorite esiletoomist ja sageli kasutatavate keelekonstruktsioonide toetust. Ja see on vaid mõned tüübiandmise eelised. Mõnele programmeerijale on see kõik peamine argument tüpiseerimise kasuks. See toob kasu kohe pärast tööle rakendamist. See tüüpide kasutamise variant ei vaja eraldi tüüpide kontrollimise süsteemi, nagu mypy, kuigi tuleb märkida, et mypy aitab säilitada annotatsioonide ja koodi vastavust.
mypy eellugu
mypy ajalugu algas Suurbritannias, Cambridge'is, mõned aastad enne, kui liitusin Dropboxiga. Õpingute raames tegin doktoritööd staatiliselt tüpiseeritud ja dünaamiliste keelte ühtlustamise teemal. Mind inspireerisid Jeremy Sieki ja Valida Taha artikkel järkjärgulisest tüpiseerimisest ning projekt Typed Racket. Otsisin võimalusi kasutada sama programmeerimiskeelt erinevates projektides — alates väikestest skriptidest kuni kodu- ja arendusteedeni, mis koosnesid paljusid miljoneid ridu. Soovis, et igasuguses projektis ei peaks tegema liiga suuri kompromisse. Oluline osa sellest oli idee järkjärgulise ülemineku kohta tüpiseerimata prototüübi loomisest ulatuslikult testitud staatiliselt tüpiseeritud valmis toote suunas. Tänapäeval peetakse neid ideid suures osas iseenesestmõistetavaks, kuid 2010. aastal oli see probleem, mida uuriti endiselt aktiivselt.
Minu algne töö tüüpide kontrollimise valdkonnas ei olnud suunatud Pythonile. Selle asemel kasutasin väikest "kodust" keelt. . Siin on näide, mis aitab teil mõista, millega tegu (tüüpide annotatsioonid siin pole kohustuslikud):
def Fib(n as Int) as Int
if n <= 1
return n
else
return Fib(n - 1) + Fib(n - 2)
end
endLihtsustatud oma keele kasutamine on tavaline lähenemine teadusuuringutes. Seda tehakse mitte viimase koha pealt just seetõttu, et see võimaldab katseid kiiresti läbi viia ning et seda, mis ei kuulu uurimise alla, saab takistamatult ignoreerida. Reaalselt kasutatavad programmeerimiskeeled on tavaliselt suured nähtused keerukate rakendustega, mis aeglustavad katseid. Kuid lihtsustatud keelel põhinevad tulemused tunduvad natuke kahtlased, kuna nende tulemuste saavutamisel võis teadlane ohverdada olulisi kaalutlusi, mis on keelte praktilise kasutamise jaoks tähtsad.
Minu tüübikontrollimise vahend Alore jaoks nägi välja väga paljutõotav, kuid ma tahtsin seda testida, tehes katseid reaalsete koodidega, mida Alore'is, võib öelda, ei olnud. Õnneks põhines Alore keel suures osas samadel ideedel kui Python. Tüübikontrollimise vahendi kohandamine Pythoni süntaksiga ja semantikaga töötamiseks oli piisavalt lihtne. See võimaldas proovida tüübikontrolli avatud lähtekoodiga Pythoni koodis. Lisaks kirjutasin transpailer, mis teisendab Alore'is kirjutatud koodi Pythoni koodiks, ja kasutasin seda, et tõlkida oma tüübikontrollimise vahendi kood. Nüüd oli mul Pythoni keeles kirjutatud tüübikontrollimise süsteem, mis toetas Pythoni alamkogumit, mingi variandi sellest keelest! (Teatud arhitektuurilised lahendused, mis Alore jaoks tähendasid mõttekust, ei sobinud hästi Pythoni jaoks, see on endiselt tuntav mõnedes mypy koodibaasi osades.)
Tegelikult ei olnud minu tüüpide süsteem sellel hetkel tõeliselt Python: see oli Python variant, mis tulenes Python 3 tüüpi annotatsioonide süntaksi mõningatest piirangutest.
See nägi välja nagu segu Java ja Pythonist:
int fib(int n):
if n <= 1:
return n
else:
return fib(n - 1) + fib(n - 2)Üks minu ideedest tol ajal oli kasutada tüüpide annotatsioone, et parandada jõudlust, kompileerides selle versiooni Pythonist C-ks või võib-olla JVM-i baitkoodiks. Ma jõudsin prototüübi kompilatori kirjutamise faasi, kuid loobusin sellest, kuna tüüpide kontroll andis endale juba piisavalt väärtust.
Lõpuks esitlesin oma projekti 2013. aasta PyCon konverentsil San Jose's. Rääkisin sellest ka Guido van Rossumiga, Pythoni helde eluaegse diktaatoriga. Ta veenis mind loobuma oma süntaksist ja kinni pidama Python 3 standardist. Python 3 toetab funktsioonide annotatsioone, seetõttu sai minu näide ümber kirjutada nii, nagu on näidatud allpool, muutudes normaalseks Python-programmiks:
def fib(n: int) -> int:
if n <= 1:
return n
else:
return fib(n - 1) + fib(n - 2)Olin sunnitud leidma teatud kompromisse (esiteks tahan märkida, et ma leidisin enda süntaksi, just seetõttu). Eelkõige ei toetanud Python 3.3, toona uusim versioon keelest, muutuja annotate. Rääkisin Guido'ga e-postiga erinevatest võimalustest sarnaste annotate kohtade süntaksiks. Otsustasime kasutama muutuja tüüpide märki kommentaarides. See aitas saavutada seatud eesmärki, kuid nägi veidi kohmakas välja (Python 3.6 andis meile meeldivama süntaksi):
products = [] # type: List[str] # WõigasTühipõhiselt kasutasid kommentaarid tüüpide jaoks Python 2 toeks, kus sisseehitatud toetust tüüpide annotate ei olnud:
f fib(n):
# type: (int) -> int
if n <= 1:
return n
else:
return fib(n - 1) + fib(n - 2)Selgus, et need (ja teised) kompromissid polnud tegelikult eriti olulised — staatilise tüüpimise eelised viisid selleni, et kasutajad unustasid kiiresti mitte just ideaalse süntaksi. Kuna nüüd kontrollitud tüüpidega Python-koodis ei kasutanud erilisi süntaktilisi konstruktsioone, töötasid olemasolevad Python'i tööriistad ja kooditöötlusprotsessid edasi normaalselt, mis kergendas arendajatel uue tööriista omandamist.
Guido suutis mind ka peale lõputöö kaitsmist Dropboxiga liituma kutsuda. Siit algab mypy loo kõige huvitavam osa.
Jätkub…
Lugupidamisega lugejad! Kui kasutate Pythonit — palun jagage, milliste suurusega projekte te selle keele peal arendate.
Allikas: habr.com
