Stien til at typetjekke 4 millioner linjer Python-kode. Del 1

I dag gør vi dig opmærksom på den første del af oversættelsen af ​​materialet om, hvordan Dropbox håndterer typekontrol af Python-kode.

Stien til at typetjekke 4 millioner linjer Python-kode. Del 1

Dropbox skriver meget i Python. Det er et sprog, som vi bruger ekstremt bredt, både til back-end-tjenester og desktop-klientapplikationer. Vi bruger også meget Go, TypeScript og Rust, men Python er vores hovedsprog. I betragtning af vores skala, og vi taler om millioner af linjer Python-kode, viste det sig, at den dynamiske indtastning af en sådan kode unødigt komplicerede dens forståelse og begyndte at påvirke arbejdsproduktiviteten alvorligt. For at afhjælpe dette problem er vi begyndt gradvist at overføre vores kode til statisk typekontrol ved hjælp af mypy. Dette er sandsynligvis det mest populære selvstændige type kontrolsystem til Python. Mypy er et open source-projekt, dets vigtigste udviklere arbejder i Dropbox.

Dropbox var en af ​​de første virksomheder, der implementerede statisk typekontrol i Python-kode i denne skala. Mypy bruges i tusindvis af projekter i disse dage. Dette værktøj utallige gange, som de siger, "testet i kamp." Vi er nået langt for at nå dertil, hvor vi er nu. Undervejs var der mange mislykkede foretagender og mislykkede eksperimenter. Dette indlæg dækker historien om statisk typekontrol i Python, fra dens stenede begyndelse som en del af mit forskningsprojekt til i dag, hvor typekontrol og typeantydning er blevet almindeligt for utallige udviklere, der skriver i Python. Disse mekanismer understøttes nu af mange værktøjer såsom IDE'er og kodeanalysatorer.

Læs anden del

Hvorfor er typekontrol nødvendig?

Hvis du nogensinde har brugt dynamisk indtastet Python, har du måske en vis forvirring om, hvorfor der har været sådan ballade omkring statisk skrivning og mypy på det seneste. Eller måske kan du lide Python netop på grund af dens dynamiske skrivning, og det der sker, gør dig simpelthen oprørt. Nøglen til værdien af ​​statisk skrivning er omfanget af løsningerne: Jo større dit projekt er, jo mere hælder du til statisk skrivning, og i sidste ende, jo mere har du virkelig brug for det.

Antag, at et bestemt projekt har nået størrelsen af ​​titusindvis af linjer, og det viste sig, at flere programmører arbejder på det. Ser vi på et lignende projekt, baseret på vores erfaring, kan vi sige, at forståelse af dets kode vil være nøglen til at holde udviklere produktive. Uden typeanmærkninger kan det være svært at finde ud af, for eksempel hvilke argumenter der skal sendes til en funktion, eller hvilke typer en funktion kan returnere. Her er typiske spørgsmål, som ofte er svære at besvare uden brug af typeanmærkninger:

  • Kan denne funktion vende tilbage None?
  • Hvad skal dette argument være? items?
  • Hvad er attributtypen id: int er det, str, eller måske en brugerdefineret type?
  • Skal dette argument være en liste? Er det muligt at give en tuple til det?

Hvis du ser på følgende type-annoterede kodestykke og prøver at besvare lignende spørgsmål, viser det sig, at dette er den enkleste opgave:

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

  • read_metadata vender ikke tilbage None, da returtypen ikke er det Optional[…].
  • argument items er en række af linjer. Det kan ikke gentages tilfældigt.
  • Egenskab id er en streng af bytes.

I en ideel verden ville man forvente, at alle sådanne finesser ville blive beskrevet i den indbyggede dokumentation (docstring). Men erfaringen giver mange eksempler på, at sådan dokumentation ofte ikke overholdes i den kode, man skal arbejde med. Selvom en sådan dokumentation er til stede i koden, kan man ikke regne med dens absolutte rigtighed. Denne dokumentation kan være vag, unøjagtig og åben for misforståelser. I store teams eller store projekter kan dette problem blive ekstremt akut.

Mens Python udmærker sig i de tidlige eller mellemliggende faser af projekter, kan succesfulde projekter og virksomheder, der bruger Python, på et tidspunkt stå over for det afgørende spørgsmål: "Skal vi omskrive alt i et statisk skrevet sprog?".

Typekontrolsystemer som mypy løser ovenstående problem ved at give udvikleren et formelt sprog til at beskrive typer, og ved at kontrollere, at typeerklæringer matcher programimplementeringen (og eventuelt ved at kontrollere deres eksistens). Generelt kan vi sige, at disse systemer stiller noget i retning af omhyggeligt kontrolleret dokumentation til vores rådighed.

Brugen af ​​sådanne systemer har andre fordele, og de er allerede helt ikke-trivielle:

  • Typekontrolsystemet kan registrere nogle små (og ikke så små) fejl. Et typisk eksempel er, når de glemmer at behandle en værdi None eller en anden speciel tilstand.
  • Kode refactoring er meget forenklet, fordi typekontrolsystemet ofte er meget præcist om, hvilken kode der skal ændres. Samtidig behøver vi ikke håbe på 100% kodedækning med test, hvilket i hvert fald normalt ikke er muligt. Vi behøver ikke dykke ned i dybden af ​​stak-sporet for at finde ud af årsagen til problemet.
  • Selv på store projekter kan mypy ofte udføre fuld typekontrol på en brøkdel af et sekund. Og udførelsen af ​​test tager normalt snesevis af sekunder eller endda minutter. Typekontrolsystemet giver programmøren øjeblikkelig feedback og giver ham mulighed for at udføre sit arbejde hurtigere. Han behøver ikke længere at skrive skrøbelige og svære at vedligeholde enhedstests, der erstatter rigtige enheder med spots og patches, bare for at få kodetestresultater hurtigere.

IDE'er og redaktører såsom PyCharm eller Visual Studio Code bruger kraften i typeannoteringer til at give udviklere kodefuldførelse, fejlfremhævning og understøttelse af almindeligt anvendte sprogkonstruktioner. Og disse er blot nogle af fordelene ved at skrive. For nogle programmører er alt dette hovedargumentet for at skrive. Dette er noget, der gavner umiddelbart efter implementering. Denne use case for typer kræver ikke et separat typekontrolsystem som mypy, selvom det skal bemærkes, at mypy hjælper med at holde typeannoteringer i overensstemmelse med kode.

Baggrund for mypy

Historien om mypy begyndte i Storbritannien, i Cambridge, et par år før jeg kom til Dropbox. Jeg har, som en del af min doktorgradsforskning, været involveret i ensretningen af ​​statisk typede og dynamiske sprog. Jeg blev inspireret af en artikel om inkrementel skrivning af Jeremy Siek og Walid Taha og af Typed Racket-projektet. Jeg forsøgte at finde måder at bruge det samme programmeringssprog til forskellige projekter – fra små scripts til kodebaser bestående af mange millioner linjer. Samtidig ville jeg sikre, at man i et projekt af enhver skala ikke skulle indgå for store kompromiser. En vigtig del af alt dette var ideen om gradvist at gå fra et utypebestemt prototypeprojekt til et omfattende testet statisk maskinskrevet færdigt produkt. I disse dage tages disse ideer stort set for givet, men i 2010 var det et problem, der stadig blev aktivt udforsket.

Mit oprindelige arbejde med typekontrol var ikke rettet mod Python. I stedet brugte jeg et lille "hjemmelavet" sprog Alore. Her er et eksempel, der vil lade dig forstå, hvad vi taler om (typeanmærkninger er valgfrie her):

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

Brug af et forenklet modersmål er en almindelig tilgang, der bruges i videnskabelig forskning. Dette er tilfældet, ikke mindst fordi det giver dig mulighed for hurtigt at udføre eksperimenter, og også på grund af det faktum, at det, der ikke har noget med undersøgelsen at gøre, nemt kan ignoreres. Programmeringssprog i den virkelige verden har en tendens til at være fænomener i stor skala med komplekse implementeringer, og dette bremser eksperimentet. Ethvert resultat baseret på et forenklet sprog ser dog lidt mistænkeligt ud, da forskeren ved at opnå disse resultater kan have ofret overvejelser, der er vigtige for den praktiske brug af sprog.

Mit typetjek til Alore så meget lovende ud, men jeg ville gerne teste det af ved at eksperimentere med rigtig kode, som man kan sige ikke var skrevet i Alore. Heldigvis for mig var Alore-sproget stort set baseret på de samme ideer som Python. Det var nemt nok at lave typecheckeren om, så den kunne arbejde med Pythons syntaks og semantik. Dette gav os mulighed for at prøve at skrive indtjekning i open source Python-kode. Derudover skrev jeg en transpiler til at konvertere kode skrevet i Alore til Python-kode og brugte den til at oversætte min typechecker-kode. Nu havde jeg et typekontrolsystem skrevet i Python, der understøttede en delmængde af Python, en slags sprog! (Visse arkitektoniske beslutninger, der gav mening for Alore, var dårligt egnede til Python, og dette er stadig mærkbart i dele af mypy-kodebasen.)

Faktisk kunne det sprog, der understøttes af mit typesystem, ikke helt kaldes Python på dette tidspunkt: det var en variant af Python på grund af nogle begrænsninger af Python 3-typens annotationssyntaks.

Det lignede en blanding af Java og Python:

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

En af mine ideer på det tidspunkt var at bruge typeannoteringer til at forbedre ydeevnen ved at kompilere denne form for Python til C, eller måske JVM-bytekode. Jeg kom til stadiet med at skrive en compiler-prototype, men jeg opgav denne idé, da selve typekontrol så ret nyttig ud.

Jeg endte med at præsentere mit projekt ved PyCon 2013 i Santa Clara. Jeg talte også om dette med Guido van Rossum, den velvillige Python-diktator for livet. Han overbeviste mig om at droppe min egen syntaks og holde mig til standard Python 3-syntaks. Python 3 understøtter funktionsannoteringer, så mit eksempel kunne omskrives som vist nedenfor, hvilket resulterer i et normalt Python-program:

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

Jeg var nødt til at indgå nogle kompromiser (først og fremmest vil jeg bemærke, at jeg opfandt min egen syntaks netop af denne grund). Især Python 3.3, den seneste version af sproget på det tidspunkt, understøttede ikke variable annoteringer. Jeg diskuterede med Guido via e-mail forskellige muligheder for syntaktisk design af sådanne annoteringer. Vi besluttede at bruge typekommentarer til variabler. Dette tjente det tilsigtede formål, men var noget besværligt (Python 3.6 gav os en pænere syntaks):

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

Typekommentarer var også nyttige for at understøtte Python 2, som ikke har indbygget understøttelse af typeanmærkninger:

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

Det viste sig, at disse (og andre) afvejninger ikke virkelig betød noget - fordelene ved statisk skrivning betød, at brugerne hurtigt glemte alt om en mindre-end-perfekt syntaks. Da der ikke blev brugt nogen specielle syntaktiske konstruktioner i den typekontrollerede Python-kode, fortsatte eksisterende Python-værktøjer og kodebehandlingsprocesser med at fungere normalt, hvilket gjorde det meget lettere for udviklere at lære det nye værktøj.

Guido overbeviste mig også om at blive medlem af Dropbox, efter at jeg havde afsluttet mit speciale. Det er her den mest interessante del af mypy-historien begynder.

Fortsættes ...

Kære læsere! Hvis du bruger Python, bedes du fortælle os om omfanget af projekter, du udvikler på dette sprog.

Stien til at typetjekke 4 millioner linjer Python-kode. Del 1
Stien til at typetjekke 4 millioner linjer Python-kode. Del 1

Kilde: www.habr.com

Tilføj en kommentar