Vägen till att typkontrollera 4 miljoner rader Python-kod. Del 1

Idag uppmärksammar vi den första delen av översättningen av materialet om hur Dropbox hanterar typkontroll av Python-kod.

Vägen till att typkontrollera 4 miljoner rader Python-kod. Del 1

Dropbox skriver mycket i Python. Det är ett språk som vi använder extremt brett, både för back-end-tjänster och stationära klientapplikationer. Vi använder också mycket Go, TypeScript och Rust, men Python är vårt huvudspråk. Med tanke på vår skala, och vi talar om miljontals rader Python-kod, visade det sig att den dynamiska skrivningen av sådan kod onödigt komplicerade förståelsen och började allvarligt påverka arbetsproduktiviteten. För att mildra detta problem har vi börjat gradvis övergå vår kod till statisk typkontroll med mypy. Detta är förmodligen det mest populära kontrollsystemet av fristående typ för Python. Mypy är ett projekt med öppen källkod, dess huvudutvecklare arbetar i Dropbox.

Dropbox var ett av de första företagen som implementerade statisk typkontroll i Python-kod i denna skala. Mypy används i tusentals projekt nuförtiden. Detta verktyg otaliga gånger, som de säger, "testat i strid." Vi har kommit långt för att komma dit vi är nu. Längs vägen fanns det många misslyckade åtaganden och misslyckade experiment. Det här inlägget täcker historien om statisk typkontroll i Python, från dess steniga början som en del av mitt forskningsprojekt, till idag, när typkontroll och typtips har blivit vardagligt för otaliga utvecklare som skriver i Python. Dessa mekanismer stöds nu av många verktyg som IDE:er och kodanalysatorer.

Läs den andra delen

Varför är typkontroll nödvändig?

Om du någonsin har använt dynamiskt skriven Python, kanske du har lite förvirring om varför det har varit så mycket väsen kring statisk typning och mypy på sistone. Eller så kanske du gillar Python just på grund av dess dynamiska skrivning, och det som händer gör dig helt enkelt upprörd. Nyckeln till värdet av statisk typning är omfattningen av lösningarna: ju större ditt projekt är, desto mer lutar du dig mot statisk typning, och i slutändan, desto mer behöver du verkligen det.

Anta att ett visst projekt har nått storleken på tiotusentals rader, och det visade sig att flera programmerare arbetar med det. Om vi ​​tittar på ett liknande projekt, baserat på vår erfarenhet, kan vi säga att förståelse av dess kod kommer att vara nyckeln till att hålla utvecklare produktiva. Utan typanteckningar kan det vara svårt att ta reda på till exempel vilka argument som ska skickas till en funktion, eller vilka typer en funktion kan returnera. Här är typiska frågor som ofta är svåra att besvara utan att använda typkommentarer:

  • Kan denna funktion gå tillbaka None?
  • Vad ska detta argument vara? items?
  • Vad är attributtypen id: int om det str, eller kanske någon anpassad typ?
  • Ska detta argument vara en lista? Är det möjligt att skicka en tupel till den?

Om du tittar på följande typkommenterade kodavsnitt och försöker svara på liknande frågor, visar det sig att detta är den enklaste uppgiften:

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

  • read_metadata kommer inte tillbaka None, eftersom returtypen inte är det Optional[…].
  • argument items är en sekvens av linjer. Det kan inte upprepas slumpmässigt.
  • Attribut id är en sträng av bytes.

I en ideal värld skulle man förvänta sig att alla sådana subtiliteter skulle beskrivas i den inbyggda dokumentationen (docstring). Men erfarenheten ger många exempel på att sådan dokumentation ofta inte följs i koden som man ska arbeta med. Även om sådan dokumentation finns i koden kan man inte räkna med dess absoluta riktighet. Denna dokumentation kan vara vag, felaktig och öppen för missförstånd. I stora team eller stora projekt kan detta problem bli extremt akut.

Medan Python utmärker sig i de tidiga eller mellanliggande stadierna av projekt, kan framgångsrika projekt och företag som använder Python vid något tillfälle ställas inför den avgörande frågan: "Ska vi skriva om allt på ett statiskt maskinskrivet språk?".

Typkontrollsystem som mypy löser ovanstående problem genom att förse utvecklaren med ett formellt språk för att beskriva typer, och genom att kontrollera att typdeklarationer matchar programimplementeringen (och, valfritt, genom att kontrollera att de finns). Generellt kan vi säga att dessa system ställer till vårt förfogande ungefär som noggrant kontrollerad dokumentation.

Användningen av sådana system har andra fördelar, och de är redan helt icke-triviala:

  • Typkontrollsystemet kan upptäcka några små (och inte så små) fel. Ett typiskt exempel är när de glömmer att bearbeta ett värde None eller något annat speciellt villkor.
  • Kodrefaktorering är avsevärt förenklad eftersom typkontrollsystemet ofta är mycket exakt om vilken kod som behöver ändras. Samtidigt behöver vi inte hoppas på 100% kodtäckning med tester, vilket i alla fall vanligtvis inte är genomförbart. Vi behöver inte fördjupa oss i djupet av stackspåret för att ta reda på orsaken till problemet.
  • Även på stora projekt kan mypy ofta göra full typkontroll på en bråkdel av en sekund. Och utförandet av tester tar vanligtvis tiotals sekunder eller till och med minuter. Typkontrollsystemet ger programmeraren omedelbar feedback och låter honom göra sitt jobb snabbare. Han behöver inte längre skriva ömtåliga och svåra att underhålla enhetstester som ersätter verkliga enheter med hånar och patchar bara för att få kodtestresultat snabbare.

IDE:er och redigerare som PyCharm eller Visual Studio Code använder kraften i typkommentarer för att ge utvecklare kodkomplettering, felmarkering och stöd för vanliga språkkonstruktioner. Och det här är bara några av fördelarna med att skriva. För vissa programmerare är allt detta huvudargumentet för att skriva. Detta är något som gynnas direkt efter implementering. Detta användningsfall för typer kräver inte ett separat typkontrollsystem som mypy, även om det bör noteras att mypy hjälper till att hålla typanteckningar konsekventa med koden.

Bakgrund av mypy

Mypys historia började i Storbritannien, i Cambridge, några år innan jag gick med i Dropbox. Jag har varit involverad, som en del av min doktorandforskning, i förenandet av statiskt typade och dynamiska språk. Jag blev inspirerad av en artikel om inkrementell maskinskrivning av Jeremy Siek och Walid Taha, och av 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 säkerställa att man i ett projekt av vilken skala som helst inte skulle behöva göra för stora kompromisser. En viktig del av allt detta var idén om att gradvis gå från ett otypat prototypprojekt till en omfattande testad statiskt typad färdig produkt. Nuförtiden tas dessa idéer till stor del för givna, men 2010 var det ett problem som fortfarande aktivt utforskades.

Mitt ursprungliga arbete med typkontroll var inte inriktat på Python. Istället använde jag ett litet "hemlagat" språk Alore. Här är ett exempel som låter dig förstå vad vi pratar om (typkommentarer är valfria här):

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 modersmål är ett vanligt tillvägagångssätt som används inom vetenskaplig forskning. Detta är så, inte minst för att det gör att du snabbt kan utföra experiment, och även på grund av att det som inte har något med studien att göra lätt kan ignoreras. Verkliga programmeringsspråk tenderar att vara storskaliga fenomen med komplexa implementeringar, och detta saktar ner experimenterandet. Alla resultat baserade på ett förenklat språk ser dock lite misstänksamma ut, eftersom forskaren vid erhållandet av dessa resultat kan ha offrat överväganden som är viktiga för den praktiska användningen av språk.

Min typkontroll för Alore såg väldigt lovande ut, men jag ville testa den genom att experimentera med riktig kod, som man kanske inte skrev i Alore. Lyckligtvis för mig var Alore-språket till stor del baserat på samma idéer som Python. Det var lätt nog att göra om typkontrollen så att den kunde fungera med Pythons syntax och semantik. Detta gjorde det möjligt för oss att testa att skriva in Python-kod med öppen källkod. Dessutom skrev jag en transpiler för att konvertera kod skriven i Alore till Python-kod och använde den för att översätta min typkontrollkod. Nu hade jag ett typkontrollsystem skrivet i Python som stödde en delmängd av Python, något slags språk! (Vissa arkitektoniska beslut som var vettiga för Alore var dåligt lämpade för Python, och detta är fortfarande märkbart i delar av mypy-kodbasen.)

Faktum är att språket som stöds av mitt typsystem kunde inte riktigt kallas Python vid denna tidpunkt: det var en variant av Python på grund av vissa begränsningar av Python 3-typanteckningssyntaxen.

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 på den tiden var att använda typkommentarer för att förbättra prestandan genom att kompilera den här typen av Python till C, eller kanske JVM-bytekod. Jag kom till stadiet att skriva en kompilatorprototyp, men jag övergav den här idén, eftersom typkontroll i sig såg ganska användbart ut.

Det slutade med att jag presenterade mitt projekt på PyCon 2013 i Santa Clara. Jag pratade också om detta med Guido van Rossum, den välvillige Python-diktatorn för livet. Han övertygade mig om att släppa min egen syntax och hålla mig till standardsyntaxen i Python 3. Python 3 stöder funktionskommentarer, så mitt exempel kan skrivas om enligt nedan, vilket resulterar 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 några kompromisser (först och främst vill jag notera att jag uppfann min egen syntax av just denna anledning). I synnerhet Python 3.3, den senaste versionen av språket vid den tiden, stödde inte variabelannoteringar. Jag diskuterade med Guido via e-post olika möjligheter för syntaktisk design av sådana kommentarer. Vi bestämde oss för att använda typkommentarer för variabler. Detta tjänade det avsedda syftet, men var något besvärligt (Python 3.6 gav oss en trevligare syntax):

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

Typkommentarer kom också till nytta för att stödja Python 2, som inte har inbyggt stöd för typkommentarer:

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) avvägningar inte spelade någon roll - fördelarna med statisk typning gjorde att användarna snart glömde bort syntax som inte var idealisk. Eftersom inga speciella syntaktiska konstruktioner användes i den typkontrollerade Python-koden, fortsatte befintliga Python-verktyg och kodbehandlingsprocesser att fungera normalt, vilket gjorde det mycket lättare för utvecklare att lära sig det nya verktyget.

Guido övertygade mig också att gå med i Dropbox efter att jag avslutat mitt examensarbete. Det är här den mest intressanta delen av mypy-berättelsen börjar.

Fortsättning ...

Kära läsare! Om du använder Python, berätta för oss om omfattningen av projekt du utvecklar på detta språk.

Vägen till att typkontrollera 4 miljoner rader Python-kod. Del 1
Vägen till att typkontrollera 4 miljoner rader Python-kod. Del 1

Källa: will.com

Lägg en kommentar