Täna esitame teile osa tõlkest materjalist, mis käsitleb seda, kuidas Dropboxis tegeletakse Python-koodi tüübikontrolliga.
Dropboxis kirjutatakse palju Pythonis. See on keel, mida me kasutame äärmiselt laialdaselt - nii tagaplaaniteenustes kui ka lauaklientrakendustes. Samuti kasutame ulatuslikult Go, TypeScripti ja Rusti, kuid Python on meie peamine keel. Arvestades meie ulatusi, millega on tegemist miljonite ridade Python-koodiga, osutus dünaamiline tüübikontroll selle koodi mõistmise osas põhjendamatult keeruliseks ning hakkas tõsiselt mõjutama töö efektiivsust. Selle probleemi leevendamiseks alustasime järkjärgulist üleminekut meie koodi staatilisele tüübikontrollile mypy kasutades. See on tõenäoliselt kõige populaarsem iseseisev tüübikontrolli süsteem Pythonile. Mypy on avatud lähtekoodiga projekt, mille peamised arendajad töötavad Dropboxis.
Dropbox on üks esimesi ettevõtteid, kes rakendas Python-koodi staatilist tüübikontrolli sellises ulatuses. Tänapäeval kasutatakse mypy-d tuhandetes projektides. See tööriist on korduvalt, nagu öeldakse, „testitud lahingus“. Meie, et jõuda sinna, kus me praegu oleme, pidime läbima pika tee. Sellel teel oli palju ebaõnnestunud algatusi ja läbi kukkunud katseid. See materjal jutustab tüübikontrolli ajaloost Pythonis - selle raskest algusest, mis oli osa minu teaduslikust uurimisprojektist, kuni tänapäeva, kui tüübikontrollid ja tüübisoovitused on muutunud tuttavaks lõputule hulgale Pythonis kirjutavatele arendajatele. Need mehhanismid on nüüd mitmete tööriistade - nagu IDE ja koodianalüsaatorid - toetavad.
→
Miks on tüübikontroll vajalik?
Kui olete kunagi kasutanud dünaamiliselt tüübitud Pythoni, võib teil olla raskusi mõista, miks statilise tüübistamise ja mypy ümber viimasel ajal nii palju müra tõusnud on. Võib-olla meeldib teile Python just selle dünaamilise tüübistamise tõttu ning see, mis toimub, lihtsalt häirib teid. Statilise tüübistamise väärtuse võti on lahenduste ulatus: mida suurem on teie projekt, seda tugevam on teie kalduvus statilisele tüübistamisele, ja lõpuks on see teile tõeliselt vajalik.
Kujutage ette, et mõni projekt on saavutanud kümneid tuhandeid ridu ja selle kallal töötab mitu programmeerijat. Sellist projekti vaadates saame oma kogemusele toetudes öelda, et koodi mõistmine on arendajate tootlikkuse hoidmise võti. Ilma tüübiteatiseta on raske selgitada näiteks, milliseid argumente tuleb funktsioonile edastada, või milliseid väärtuste tüüpe võiks mõni funktsioon tagastada. Siin on tüüpilised küsimused, millele on sageli raske vastata ilma tüübiteatisteta:
- Kas see funktsioon võib tagastada
None? - Milline peaks olema see argument
items? - Mis on selle atribuutide tüüp
id:intkas see onstr, või võib-olla mõni kasutaja määratud tüüp? - Kas see argument peab olema loend? Kas saab edastada tupel?
Kui vaatame järgmist koodilõiku, mis on varustatud tüübiteatistega, ja proovime vastata sarnastele küsimustele, selgub, et see on lihtne ülesanne:
class Resource:
id: bytes
...
def read_metadata(self,
items: Sequence[str]) -> Dict[str, MetadataItem]:
...read_metadataei tagastaNone, kuna tagastatav tüüp ei oleOptional[…].- Argumendiks
items— see on stringide jada. Seda ei saa iteratsiooni käigus suvalises järjekorras töötada. - elemendi nüüd
id— see on baitide string.
Ideaalses maailmas võiks oodata, et kõik sarnased nüansid oleksid dokumenteeritud sisemise dokumentatsiooni (docstring) kaudu. Kuid kogemus toob palju näiteid selle kohta, et sellist dokumentatsiooni koodis, millega tuleb töötada, ei pruugi sageli olla. Ieven kui selline dokumentatsioon on olemas, ei saa loota selle absoluutsele õiguspärasusele. Selline dokumentatsioon võib olla ebaselge, vale või jätta palju võimalusi vale arusaamise tekkeks. Suurtes meeskondades või suurtes projektides võib see probleem muutuda äärmiselt teravaks.
Kuigi Python näitab end suurepäraselt projektide varajastes või vahepeetavates etappides, võivad edukad projektid ja ettevõtted, kes kasutavad Pythoni, teatud hetkel silmitsi seista eluliselt olulise küsimusega: „Kas peame kõik staatiliselt tüpitud keeles ümber kirjutama?”.
Tüübi kontrollimise süsteemid nagu mypy lahendavad eespool mainitud probleemid, pakkudes arendajatele formaalset keelt tüüpide kirjeldamiseks ning kontrollides, kas tüüpide kirjeldused vastavad programmide rakendusele (ja mis ei ole kohustuslik, ka nende olemasolu). Üldiselt võib öelda, et need süsteemid annavad meile midagi, mis sarnaneb hoolikalt kontrollitud dokumentatsiooniga.
Selliste süsteemide kasutamisel on ka teisi eeliseid, mis on juba täiesti mittetriviaalsed:
- Tüüpide kontrollimise süsteem suudab tuvastada teatud väikeseid (ja ka mitte eriti väikeseid) vigu. Tüüpiline näide on see, kui unustatakse töötada väärtuse üle
Nonevõi mõne muu erandi üle. - Koodi refaktoreerimine muutub oluliselt lihtsamaks, kuna tüüpide kontrollimise süsteem annab sageli väga täpset teavet selle kohta, millist koodi tuleb muuta. Samuti ei pea me lootma 100% koodi testimise katvusele, mis igal juhul on tavaliselt saavutatav. Me ei pea süvenema virnastamisraportite sügavustesse, et selgitada välja rikkumise põhjus.
- Isegi suurtes projektides suudab mypy tihti tüüpe täielikult kontrollida sekunditega. Testide läbiviimine võtab tavaliselt aga kümneid sekundeid või isegi minuteid. Tüüpide kontrollimise süsteem annab programmeerijale kohese tagasiside ja võimaldab tal oma tööd kiiremini teha. Tal ei ole enam vaja kirjutada hapra ja hooldamisega raskesti hallatavaid mooduliteste, mis asendavad tõelised objektiid moke ja plaastritega, et kiiremini koodi testimise tulemusi saada.
IDE-d ja redigeerijad, nagu PyCharm või Visual Studio Code, kasutavad tüübiannotatsioonide võimalusi, et pakkuda arendajatele koodi automaatse lõpetamise, vigade esiletõstmise ja sageli kasutatavate keelekonstruktsioonide toetamise võimalusi. Ja see on vaid mõned plussid, mida tüüpide määramine pakub. Mõnele programmeerijale on see peamine argument tüüpide poole kaldumise kasuks. See toob kasu kohe pärast tööle rakendamist. See tüüpide kasutamise variant ei nõua eraldi tüübi kontrollimise süsteemi, nagu mypy, kuigi tuleb märkida, et mypy aitab hoida tüübiannotatsioonide ja koodi vastavust.
mypy taustalugu
mypy ajalugu algas Suurbritannias, Cambridge'is, mõned aastad enne, kui ma Dropboxiga liitusin. Tegeleksin oma doktoritöö raames staatiliselt tüpiseeritud ja dünaamiliste keelte ühtlustamise küsimustega. Mind inspireeris Jeremy Sieki ja Valida Thahi artikkel järkjärgulise tüpiseerimise kohta ning projekt Typed Racket. Püüdlesin leida viise sama programmeerimiskeele kasutamiseks erinevates projektides — alates väikestest skriptidest kuni koodibaasidesse, mis koosnevad paljusid miljoneid ridu koodist. Samal ajal soovis ma, et mis tahes suuruses projektis ei oleks vaja teha liiga suuri kompromisse. Oluline osa sellest kõik oli päevakajalise arengusuuna mõte, kus liikuda türpiseerimata projekti prototüübist põhjalikult testitud staatiliselt tüpiseeritud valmistoote suunas. Tänapäeval võetakse neid ideid suuresti iseenesestmõistetavalt, kuid 2010. aastal oli see probleem, mida veel aktiivselt uuriti.
Minu algne töö tüüpide kontrollimisel ei olnud suunatud Pythonile. Selle asemel kasutasin väikest "isetehtud" keelt. Siin on näide, mis aitab teil mõista, millest jutt käib (tüüb annotateeringud ei ole siin kohustuslikud):
def Fib(n: int) -> int:
if n <= 1:
return n
else:
return Fib(n - 1) + Fib(n - 2)
end
endLihtsustatud reeglite keele kasutamine on tavaline lähenemine teadusuuringutes. See on tänu sellele, et see võimaldab kiiresti katseid läbi viia, samuti on see seetõttu, et seda, mis ei ole uurimisega seotud, saab vabalt ignoreerida. Reaalses kasutuses olevad programmeerimised on tavaliselt suurte ja keeruliste rakendustega, mis aeglustavad katsete tegemist. Siiski näevad soovitavad tulemused, mis põhinevad lihtsustatud keelel, alati kahtlased, kuna nende tulemuste saavutamise käigus võivad uurijad jätta tähelepanuta praktikas olulised kaalutlused.
Minu tüübi kontrollimise tööriist Alore'ile nägi välja väga paljutõotav, kuid tahtsin seda testida, viies läbi katseid reaalsete koodide osas, millest võib öelda, et Alore'i peaaegu ei olnud. Õnneks põhines Alore keel suuresti samadel ideedel nagu Python. Tööriista tüübi kontrolli kohandamine Python'i süntaksiga töötamiseks oli piisavalt lihtne. See võimaldas proovida tüübikontrolli avatud lähtekoodiga Python'i koodis. Lisaks kirjutasin transpaileri, et konverteerida Alore keeles kirjutatud kood Python'i koodiks ja kasutasin seda oma tüübikontrolli tööriista koodi tõlkimiseks. Nüüd oli mul tüübikontrolli süsteem, mis oli kirjutatud Python'is ja toetas Python'i alamsüntaksit, teatud versiooni sellest keelest! (Teatud arhitektuursed lahendused, mis olid mõistlikud Alore'i puhul, ei sobinud hästi Python'i jaoks, see kajastub ikka veel mõnes mypy koodibaasi osas.)
Tegelikult ei saanud minu tüübisüsteem toetatud keel sel hetkel päris õigusega Python'i nimetada: see oli Python'i variant, kuna Python 3 tüübikontrolli süntaksi piirangud.
See nägi välja nagu Java ja Python'i segu:
int fib(int n):
if n <= 1:
return n
else:
return fib(n - 1) + fib(n - 2)Üks minu ideedest sel ajal oli kasutada tüübianotatsioone tõhususe parandamiseks, kompileerides seda Pythonit C-ks või võib-olla JVM-i baitkoodiks. Jõudsin prototüüpi kompilaatori kirjutamise faasi, kuid loobusin sellest ideest, kuna tüübikontroll ise tundus piisavalt kasulik.
Lõpuks esitlesin oma projekti PyCon 2013 konverentsil San Jose's. Rääkisin sellest ka Guido van Rossumiga, põhimõttelise eluajadse Python'i diktaatori, kes veenis mind loobuma oma süntaksist ja jääma standardse Python 3 süntaksi juurde. Python 3 toetab funktsioonide anotatsioone, mistõttu sain oma näite kirjutada alljärgnevalt, saades normaalse Python-programmi:
def fib(n: int) -> int:
if n <= 1:
return n
else:
return fib(n - 1) + fib(n - 2)Pidin minema teatud kompromissidele (esiteks tahan märkida, et just sellepärast leiutasin oma süntaksi). Esiteks, Python 3.3, toona kõige uuem versioon, ei toetanud muutujate anotatsioone. Arutasin Guidoga e-kirjaga erinevaid võimalusi selliste anotatsioonide süntaksiks. Otsustasime kasutada muutujate tüüpide näitamiseks kommentaare. See saavutab soovitud eesmärgi, kuid näeb välja veidi mahukas (Python 3.6 andis meile toredama süntaksi):
products = [] # type: List[str] # EwwTüüpide kommentaarid tulid kasuks ka Python 2 toetamiseks, kus pole sisseehitatud tüübianotatsioonide tuge:
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 olulised — staatilise tüübikontrolli eelised viisid selleni, et kasutajad unustasid varsti mitteideaalse süntaksi. Kuna nüüd Python-koodis, kus kontrolliti tüüpe, ei rakendatud erilisi süntaktilisi konstruktsioone, jätkasid olemasolevad Python'i tööriistad ja koodiprotsessid tavaliselt normaalselt töötamist, mis muudab arendajate jaoks uue tööriista valdama palju lihtsamaks.
Guido veenis mind lisanduma Dropboxi pärast seda, kui olin kaitsnud oma lõputöö. Siit hakkavadki mypy loo kõige huvitavamad osad.
Jätkub…
Lugupeetud lugejad! Kui kasutate Pythonit, palume rääkida, milliste projektide ulatuses te seda keelt kasutate.
Allikas: habr.com
