I dag tilbyr vi deg den første delen av oversettelsen av materialet om hvordan Dropbox håndterer typekontroll av Python-kode.
I Dropbox skriver vi mye i Python. Dette er et språk vi bruker ekstremt mye — både til backend-tjenester og skrivebordsapplikasjoner. Vi bruker også Go, TypeScript og Rust i stor utstrekning, men Python er vårt viktigste språk. Med tanke på omfanget vårt, som omhandler millioner av linjer med Python-kode, har det vist seg at dynamisk typet kode gjør forståelsen unødvendig komplisert og begynner å påvirke produktiviteten alvorlig. For å løse dette problemet begynte vi gradvis å konvertere koden vår til statisk typekontroll ved hjelp av mypy. Dette er sannsynligvis det mest populære fristående typekontrollsystemet for Python. Mypy er et open source-prosjekt, og de viktigste utviklerne jobber i Dropbox.
Dropbox ble en av de første selskapene som implementerte statisk typekontroll i Python-kode i så stor skala. I dag brukes mypy i tusenvis av prosjekter. Dette verktøyet har blitt «testet i kamp» utallige ganger. For å komme dit vi er i dag, har vi måttet gå en lang vei. På denne veien har det vært mange mislykkede forsøk og eksperimenter som ikke ledet frem. Dette materialet forteller historien om statisk typekontroll i Python — fra den tidlige og utfordrende starten, som var en del av prosjektarbeidet mitt, til i dag, da typekontroller og typehinting har blitt vanlig for et stort antall utviklere som skriver i Python. Disse mekanismene støttes nå av mange verktøy — som IDE-er og kodeanalysatorer.
→
Hvorfor er typekontroll viktig?
Dersom du noen gang har brukt det dynamisk typede Python - kan det oppstå en viss forvirring om hvorfor det har vært så mye oppmerksomhet rundt statisk typisering og mypy i det siste. Kanskje er det slik at du liker Python nettopp på grunn av dens dynamiske typisering, og det som skjer bare frustrerer deg. Nøkkelen til verdien av statisk typisering er omfanget av løsningene: Jo større prosjektet ditt er, jo mer vil du vende deg mot statisk typisering, og til slutt vil du virkelig ha behov for det.
La oss si at et prosjekt har vokst til titusenvis av linjer, og det viser seg at flere programmerere jobber med det. Når vi vurderer et slikt prosjekt, kan vi basert på vår erfaring si at forståelsen av koden vil være nøkkelen til å opprettholde utviklernes produktivitet. Uten typeannotasjoner er det ikke alltid lett å finne ut, for eksempel, hvilke argumenter som må sendes til en funksjon, eller hvilke typer verdier en viss funksjon kan returnere. Her er typiske spørsmål som ofte kan være vanskelige å besvare uten bruk av typeannotasjoner:
- Kan denne funksjonen returnere
None? - Hva skal dette argumentet være
elementer? - Hva er typen til attributtet
id:inter det,str, eller kanskje en tilpasset type? - Skal dette argumentet være en liste? Kan jeg sende inn et tuple?
Hvis vi ser på følgende kodebit, utstyrt med typeannotasjoner, og prøver å svare på slike spørsmål, vil det vise seg at dette er en enkel oppgave:
class Resource:
id: bytes
...
def read_metadata(self,
items: Sequence[str]) -> Dict[str, MetadataItem]:
...read_metadatareturnerer ikkeNone, siden returtypen ikke erOptional[…].- Argument
elementer— dette er en sekvens av strenger. Den kan ikke itereres i vilkårlig rekkefølge. - Attributtet
id— er en byte-streng.
I en perfekt verden ville man forvente at alle slike detaljer ville være beskrevet i den innebygde dokumentasjonen (docstring). Men erfaring gir mange eksempler på at slik dokumentasjon ofte mangler i koden man jobber med. Selv om slik dokumentasjon finnes i koden, kan man ikke regne med dens absolutte korrekthet. Denne dokumentasjonen kan være uklar, unøyaktig og gi rikelig rom for misforståelser. I store team eller store prosjekter kan dette problemet bli akutt.
Selv om Python fungerer utmerket i tidlige eller mellomliggende faser av prosjekter, kan vellykkede prosjekter og selskaper som bruker Python, på et tidspunkt møte det livsviktige spørsmålet: «Må vi omskrive alt til et statisk typeverktøy?»
Typekontrollsystemer som mypy løser det nevnte problemet ved å gi utviklere et formelt språk for å beskrive typer, og ved å sjekke at typebeskrivelsene stemmer overens med implementasjonen av programmene (og, om ønskelig, kontrollerer deres eksistens). Generelt kan man si at disse systemene gir oss noe liknende som grundig verifisert dokumentasjon.
Bruken av slike systemer har også andre fordeler, og de er allerede ganske ikke-trivielle:
- Typekontrollsystemet kan oppdage noen små (og også ikke så små) feil. Et typisk eksempel er når man glemmer å håndtere et verdil.
Noneeller en annen spesiell tilstand. - Refaktorisering av koden forenkles betydelig, siden typekontrollsystemet ofte gir nøyaktige indikasjoner på hvilken kode som må endres. Vi trenger ikke å stole på 100 % dekning av koden med tester, noe som uansett vanligvis er umulig. Vi trenger ikke å grave oss gjennom dybdene av stakksporingrapporter for å finne årsaken til en feil.
- Selv i store prosjekter kan mypy ofte utføre en fullstendig typekontroll på brøkdeler av et sekund. Å kjøre tester tar vanligvis tiår og minutter. Typekontrollsystemet gir programmereren øyeblikkelig tilbakemelding og lar ham gjøre jobben raskere. Han trenger ikke lenger å skrive skjøre og vanskeligstøttede enhetstester som erstatter ekte entiteter med mocks og patches bare for å få raske resultater fra kodeprøving.
IDEer og editorer som PyCharm eller Visual Studio Code benytter seg av mulighetene med typeannotasjoner for å gi utviklerne funksjoner som automatisk fullføring av kode, feilmarkering og støtte for ofte brukte språkkonstruksjoner. Og dette er bare noen av fordelene med typetyping. For noen programmerere er alt dette det viktigste argumentet for typetyping. Dette er noe som gir fordeler med en gang etter implementeringen. Denne bruken av typer krever ikke bruk av et eget typekontrollsystem som mypy, selv om det må nevnes at mypy hjelper med å opprettholde samsvar mellom typeannotasjoner og kode.
Bakgrunn for mypy
Historien til mypy begynte i Storbritannia, i Cambridge, flere år før jeg ble med i Dropbox. Jeg arbeidet, som del av et doktorgradsprosjekt, med spørsmål om forening av statisk typede og dynamiske språk. Jeg ble inspirert av en artikkel av Jeremy Siek og Walid Taha om gradvis typing, samt prosjektet Typed Racket. Jeg prøvde å finne måter å bruke det samme programmeringsspråket til ulike prosjekter — fra små skript til kodebaser bestående av mange millioner linjer. Jeg ønsket at prosjekter av enhver størrelse ikke skulle kreve for store kompromisser. En viktig del av dette var ideen om gradvis overgang fra en typeløs prototype av prosjektet til et grundig testet, statisk typet ferdig produkt. I dag er disse ideene i stor grad tatt for gitt, men i 2010 var det et problem som fortsatt ble aktivt utforsket.
Mitt opprinnelige arbeid med typekontroll var ikke rettet mot Python. I stedet brukte jeg et lite «hjemmelaget» språk Her er et eksempel som vil gjøre deg i stand til å forstå hva det dreier seg om (typeannoteringer her er valgfritt):
def Fib(n as Int) as Int
if n <= 1
return n
else
return Fib(n - 1) + Fib(n - 2)
end
endBruken av et forenklet språk utviklet av oss selv er en vanlig tilnærming brukt i vitenskapelig forskning. Dette er ikke minst fordi det muliggjør rask eksperimentering, og fordi det som ikke har noe med forskningen å gjøre, kan ignoreres uten problemer. Egentlige programmeringsspråk utgjør vanligvis omfattende fenomen med komplekse implementeringer, noe som kan bremse eksperimentene. Imidlertid kan alle resultater basert på et forenklet språk virker noe mistenkelige, siden forskeren muligens har ofret hensyn som er viktige for praktisk bruk av språk.
Min typekontrollverktøy for Alore så veldig lovende ut, men jeg ønsket å teste det ved å kjøre eksperimenter med reell kode, som det kan sies at det ikke fantes skrevet på Alore. Heldigvis var Alore-språket i stor grad basert på de samme ideene som Python. Det var enkelt å tilpasse typekontrollverktøyet slik at det kunne fungere med syntaksen og semantikken til Python. Dette gjorde det mulig å prøve å utføre typekontroll på åpen kildekode Python-kode. I tillegg skrev jeg en transpiler for å konvertere kode skrevet i Alore til Python-kode og brukte den til å oversette koden til mitt typekontrollverktøy. Nå hadde jeg et typekontrollsystem skrevet i Python som støttet et delsett av Python, en slags variant av dette språket! (Visse arkitektoniske valg som ga mening for Alore, passet dårlig for Python, noe som fortsatt er merkbart i enkelte deler av mypy-kodebasen.)
I virkeligheten kunne språket som ble støttet av mitt typesystem på dette tidspunktet ikke kalles Python: det var en variant av Python på grunn av visse begrensninger i syntaksen for typeannotasjoner i Python 3.
Det så ut som en blanding mellom Java og Python:
int fib(int n):
if n <= 1:
return n
else:
return fib(n - 1) + fib(n - 2)En av ideene mine på den tiden var å bruke typeannotasjoner for å forbedre ytelsen ved å kompilere denne varianten av Python til C, eller kanskje til JVM-bytekode. Jeg kom til stadiet med å skrive en prototyp av kompilatoren, men ga opp denne ideen, da typekontrollen så ut til å være nyttig i seg selv.
Til slutt presenterte jeg prosjektet mitt på PyCon 2013 i Santa Clara. Jeg diskuterte også dette med Guido van Rossum, den generøse livslange diktatoren for Python. Han overtalte meg til å gi opp mitt eget syntaks og følge den standard Python 3-syntaksen. Python 3 støtter funksjonsannotasjoner, noe som gjorde at eksempelet mitt kunne omskrives som vist nedenfor, og dermed bli et vanlig Python-program:
def fib(n: int) -> int:
if n <= 1:
return n
else:
return fib(n - 1) + fib(n - 2)Jeg måtte gå på noen kompromisser (for det første vil jeg nevne at jeg oppfant min egen syntaks nettopp derfor). Spesielt støttet ikke Python 3.3, den nyeste versjonen av språket på den tiden, variabelannotasjoner. Jeg diskuterte forskjellige syntaksalternativer for slike annotasjoner med Guido via e-post. Vi ble enige om å bruke kommentarer med typeangivelser for variablene. Dette oppnådde målet, men så noe klønete ut (Python 3.6 ga oss en mer behagelig syntaks):
products = [] # type: List[str] # EwwTypekommentarer var også nyttige for å støtte Python 2, som ikke har innebygd støtte for typeannotasjoner:
def fib(n):
# type: (int) -> int
if n <= 1:
return n
else:
return fib(n - 1) + fib(n - 2)Det viste seg at disse (og andre) kompromissene egentlig ikke hadde så stor betydning — fordelene med statisk typetesting førte til at brukerne snart glemte det ikke helt ideelle syntaksen. Siden det nå ikke ble brukt spesielle syntaksbyggesteiner i Python-kode der typene ble kontrollert, fortsatte eksisterende Python-verktøy og prosesser for kodebehandling å fungere normalt, noe som betydelig lette overgangen for utviklerne til det nye verktøyet.
Guido overtalte meg også til å bli med i Dropbox etter at jeg forsvarte avhandlingen min. Her begynner det mest interessante i historien om mypy.
Fortsettelse følger...
Kjære lesere! Hvis du bruker Python, vennligst fortell oss om hvilke prosjekter du utvikler på dette språket og deres omfang.
Kilde: habr.com
