VĂ€gen till att verifiera typer i 4 miljoner rader Python-kod. Del 1

Idag presenterar vi den första delen av översÀttningen av materialet om hur Dropbox arbetar med typkontroll av Python-kod.

VĂ€gen till att verifiera typer i 4 miljoner rader Python-kod. Del 1

PĂ„ Dropbox skriver vi mycket i Python. Det Ă€r ett sprĂ„k vi anvĂ€nder extremt mycket – bĂ„de för backend-tjĂ€nster och för skrivbordsapplikationer. Vi anvĂ€nder ocksĂ„ i stor utstrĂ€ckning Go, TypeScript och Rust, men Python Ă€r vĂ„rt huvudsprĂ„k. Med tanke pĂ„ vĂ„r storlek, som handlar om miljontals rader Python-kod, har den dynamiska typningen av sĂ„dan kod visat sig onödigt komplicera förstĂ„elsen och börjat pĂ„verka produktiviteten negativt. För att mildra detta problem började vi gradvis översĂ€tta vĂ„r kod till statisk typkontroll med hjĂ€lp av mypy. Detta Ă€r troligen det mest populĂ€ra oberoende typkontrollsystemet för Python. Mypy Ă€r ett open source-projekt vars huvudutvecklare arbetar pĂ„ Dropbox.

Dropbox var ett av de första företagen som implementerade statisk typkontroll i Python-kod i sĂ„dan skala. Numera anvĂ€nds mypy i tusentals projekt. Det hĂ€r verktyget har testats otaliga gĂ„nger, sĂ„ att sĂ€ga, 'i strid'. För att komma dit vi Ă€r idag har vi fĂ„tt gĂ„ en lĂ„ng vĂ€g. PĂ„ den vĂ€gen har det funnits mĂ„nga misslyckade initiativ och misslyckade experiment. Detta material berĂ€ttar historien om den statiska typkontrollen i Python – frĂ„n dess krĂ„ngliga början, som var en del av mitt vetenskapliga forskningsprojekt, till idag, dĂ„ typkontroller och typförslag har blivit vanliga för otaliga utvecklare som skriver i Python. Dessa mekanismer stöds nu av mĂ„nga verktyg – som IDE:er och kodanalysatorer.

→ LĂ€s del tvĂ„

Varför behövs typkontroll?

Om du nĂ„gonsin har anvĂ€nt det dynamiskt typade Python — kan du ha en viss förvirring över varför det har blivit sĂ„ mycket uppstĂ„ndelse kring statisk typning och mypy pĂ„ sistone. Kanske gillar du Python just pĂ„ grund av dess dynamiska typning, och det som hĂ€nder gör dig helt enkelt irriterad. Nyckeln till vĂ€rdet av statisk typning Ă€r skalet av lösningar: ju större ditt projekt Ă€r, desto mer lutar du mot statisk typning, och i slutĂ€ndan, desto mer behöver du det verkligen.

Anta att ett visst projekt har nÄtt storleken av tiotusentals rader, och det visar sig att flera programmerare arbetar pÄ det. NÀr vi betraktar ett sÄdant projekt kan vi, baserat pÄ vÄr erfarenhet, sÀga att förstÄelsen av dess kod blir avgörande för att upprÀtthÄlla utvecklarnas produktivitet. Utan typannoteringar Àr det ofta svÄrt att ta reda pÄ vilka argument som ska skickas till en funktion, eller vilka typer vÀrden en viss funktion kan returnera. HÀr Àr typiska frÄgor som ofta Àr svÄra att besvara utan typannoteringar:

  • Kan denna funktion returnera None?
  • Vad ska detta argument vara? items?
  • Vad Ă€r typen av attributet? id: int Ă€r det, str, eller kanske nĂ„gon anpassad typ?
  • Ska detta argument vara en lista? Kan en tuple passeras till det?

Om du ser pÄ följande kodsnutt, försÄld med typannoteringar, och försöker besvara sÄdana frÄgor, visar det sig att det Àr en enkel uppgift:

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

  • read_metadata returnerar inte None, eftersom returtypen inte Ă€r Optional[
].
  • Argumentet items — det Ă€r en sekvens av strĂ€ngar. Den kan inte itereras i godtycklig ordning.
  • Attributet id Ă€r en byte-strĂ€ng.

I en perfekt vĂ€rld skulle man kunna förvĂ€nta sig att alla sĂ„dana subtiliteter skulle beskrivas i den inbyggda dokumentationen (docstring). Men erfarenheten ger mĂ„nga exempel pĂ„ att en sĂ„dan dokumentation ofta saknas i den kod man mĂ„ste arbeta med. Även om sĂ„dan dokumentation finns i koden kan man inte rĂ€kna med att den Ă€r helt korrekt. Denna dokumentation kan vara otydlig, felaktig och lĂ€mna mycket utrymme för missförstĂ„nd. I stora team eller i stora projekt kan detta problem bli extremt pĂ„tagligt.

Även om Python fungerar utmĂ€rkt under de tidiga eller mellanstadierna av projekt, kan framgĂ„ngsrika projekt och företag som anvĂ€nder Python i ett visst skede stĂ€llas inför en livsviktig frĂ„ga: "MĂ„ste vi skriva om allt i ett statiskt typat sprĂ„k?".

Typkontrollsystem som mypy löser det ovan nÀmnda problemet genom att ge utvecklare ett formellt sprÄk för att beskriva typer och genom att kontrollera att typbeskrivningarna stÀmmer överens med implementeringen av programmen (och, vilket inte Àr nödvÀndigt, kontrollerar deras existens). Generellt kan man sÀga att dessa system ger oss nÄgot liknande noggrant verifierad dokumentation.

AnvÀndningen av sÄdana system har Àven andra fördelar som redan Àr helt icke-triviala:

  • Typkontrollsystemet kan upptĂ€cka vissa smĂ„fel (och Ă€ven större fel). Ett typiskt exempel Ă€r nĂ€r man glömmer att hantera ett vĂ€rde None eller nĂ„got annat sĂ€rskilt villkor.
  • Refaktorering av koden blir betydligt enklare, eftersom typkontrollsystemet ofta ger mycket exakt information om vilken kod som behöver Ă€ndras. Vi behöver inte heller förlita oss pĂ„ att ha 100% testtĂ€ckning av koden, vilket i alla fall vanligtvis Ă€r ogenomförbart. Vi behöver inte grĂ€va ner oss i djupet av stacktrace-rapporter för att ta reda pĂ„ vad som orsakar problemet.
  • Även i stora projekt kan mypy ofta genomföra en fullstĂ€ndig typkontroll pĂ„ brĂ„kdelen av en sekund. Att köra tester tar vanligtvis tiotals sekunder eller till och med minuter. Typkontrollsystemet ger programmeraren omedelbar Ă„terkoppling och gör att denne kan arbeta snabbare. Det Ă€r inte lĂ€ngre nödvĂ€ndigt att skriva ömtĂ„liga och svĂ„ra att underhĂ„lla modulĂ€ra tester som ersĂ€tter riktiga entiteter med mocks och patches bara för att snabbt fĂ„ resultat av kodtester.

IDE:er och redaktörer som PyCharm eller Visual Studio Code anvÀnder möjligheterna med typannoteringar för att ge utvecklare funktioner som automatisk kodslutförning, felmarkering och stöd för ofta anvÀnda sprÄkstrukturer. Det hÀr Àr bara nÄgra av fördelarna med typning. För vissa programmerare Àr allt detta det frÀmsta argumentet för typning. Det Àr nÄgot som ger fördelar sÄ snart det implementeras i arbetet. Denna anvÀndning av typer krÀver inte en separat typkontrollsystem som mypy, Àven om det bör nÀmnas att mypy hjÀlper till att sÀkerstÀlla att typannoteringar och kod stÀmmer överens.

Bakgrunden till mypy

Historien om mypy började i Storbritannien, i Cambridge, flera Ă„r innan jag gick med i Dropbox. Jag arbetade, inom ramen för min doktorandstudie, med att enhetligt betrakta statiskt typade och dynamiska sprĂ„k. Jag inspirerades av en artikel om gradvis typning av Jeremy Siek och Walid Taha, samt projektet Typed Racket. Jag försökte hitta sĂ€tt att anvĂ€nda samma programmeringssprĂ„k för olika projekt – frĂ„n smĂ„ skript till kodbaser bestĂ„ende av mĂ„nga miljoner rader. Samtidigt ville jag att nĂ„got av projektets storlek skulle kunna genomföras utan alltför stora kompromisser. En viktig del av allt detta var idĂ©n om en gradvis övergĂ„ng frĂ„n en otypad prototyp till en grundligt testad statiskt typad fĂ€rdig produkt. I dag tas dessa idĂ©er till stor del för givna, men 2010 var det fortfarande ett problem som aktivt utforskades.

Mitt initiala arbete inom typkontroll var inte inriktat pÄ Python. IstÀllet anvÀnde jag ett litet "hemgjort" sprÄk Alore. HÀr Àr ett exempel som hjÀlper dig att förstÄ vad som avses (typnoteringar Àr hÀr valfria):

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

Att anvÀnda ett förenklat sprÄk av egen utveckling Àr en vanlig metod inom vetenskaplig forskning. Detta beror i hög grad pÄ att det möjliggör snabba experiment, och att det som inte Àr relevant för forskningen kan ignoreras utan problem. Faktiska programmeringssprÄk Àr vanligtvis omfattande fenomen med komplexa implementationer, vilket saktar ner experimentprocessen. Men resultaten baserade pÄ ett förenklat sprÄk verkar lite misstÀnksamma, eftersom forskaren, i sitt arbete, kan ha offrat övervÀganden som Àr viktiga för praktisk anvÀndning av sprÄken.

Mitt typkontrollverktyg för Alore verkade lovande, men jag ville testa det genom att utföra experiment med riktig kod, som det kan sÀgas inte fanns för Alore. Lyckligtvis var Alore sprÄket i stor utstrÀckning baserat pÄ samma idéer som Python. Det var ganska enkelt att omarbeta typkontrollverktyget sÄ att det kunde fungera med Python-syntax och semantik. Detta gjorde det möjligt att försöka utföra typkontroll pÄ öppen kÀllkod Python-kod. Dessutom skrev jag en transpiler för att omvandla kod skriven i Alore till Python-kod och anvÀnde den för att översÀtta koden för mitt typkontrollverktyg. Nu hade jag ett typkontrollsystem skrivet i Python som stödde en delmÀngd av Python, en slags variant av detta sprÄk! (Vissa arkitektoniska beslut som var rimliga för Alore passade dÄligt för Python, vilket fortfarande mÀrks i vissa delar av mypy-kodbasen.)

Faktum Àr att sprÄket som stöddes av mitt typystem vid denna tidpunkt inte riktigt kunde kallas Python: det var en variant av Python pÄ grund av vissa syntaxbegrÀnsningar i typnoteringarna för Python 3.

Det sÄg ut som en blandning av Java och Python:

int fib(int n):
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

En av mina idéer dÄ var att anvÀnda typannoteringar för att förbÀttra prestandan genom att kompilera denna variant av Python till C, eller kanske till JVM-bytekod. Jag kom sÄ lÄngt som till att skriva en prototyp av en kompilator, men övergav den idén eftersom typkontrollen visade sig vara tillrÀckligt anvÀndbar i sig sjÀlv.

Jag presenterade slutligen mitt projekt pÄ PyCon 2013 i Santa Clara. Jag diskuterade ocksÄ detta med Guido van Rossum, den generösa livstidens diktator i Python. Han övertygade mig att ge upp min egen syntax och hÄlla mig till standard Python 3-syntax. Python 3 stöder funktionsannoteringar, vilket gör att mitt exempel kunde skrivas om som visas nedan, vilket resulterade i ett normalt Python-program:

def fib(n: int) -> int:
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

Jag var tvungen att göra vissa kompromisser (jag vill sÀrskilt pÄpeka att jag uppfann min egen syntax just dÀrför). Specifikt stödde Python 3.3, den senaste versionen av sprÄket vid den tiden, inte variabelannoteringar. Jag diskuterade med Guido via e-post olika möjligheter till syntax för sÄdana annoteringar. Vi beslöt oss för att anvÀnda kommentarer för variabler som angav typer. Detta uppnÄddes mÄlet, men sÄg ganska otympligt ut (Python 3.6 gav oss en mer trevlig syntax):

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

Typkommentarer var dessutom anvÀndbara för att stödja Python 2, som inte har inbyggt stöd för typannoteringar:

f fib(n):
    # type: (int) -> int
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

Det visade sig att dessa (och andra) kompromisser egentligen inte hade nĂ„gon större betydelse — fördelarna med statisk typkontroll ledde till att anvĂ€ndarna snart glömde den inte helt perfekta syntaxen. Eftersom nu i Python-kod, dĂ€r typer var kontrollerade, inte anvĂ€ndes nĂ„gra speciella syntaxkonstruktioner, fortsatte existerande Python-verktyg och processer för kodhantering att fungera normalt, vilket betydligt underlĂ€ttade för utvecklarna att lĂ€ra sig det nya verktyget.

Guido övertygade dessutom mig att gÄ med i Dropbox efter att jag hade försvarat min examensarbete. HÀr börjar det mest intressanta i historien om mypy.

FortsĂ€ttning följer


KÀra lÀsare! Om du anvÀnder Python, ber vi dig berÀtta om vilka projekt du utvecklar pÄ detta sprÄk.

VĂ€gen till att verifiera typer i 4 miljoner rader Python-kod. Del 1
VĂ€gen till att verifiera typer i 4 miljoner rader Python-kod. Del 1

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster