Put do provjere tipa 4 milijuna linija Python koda. 1. dio

Danas vam predstavljamo prvi dio prijevoda materijala o tome kako se Dropbox bavi kontrolom tipa Python koda.

Put do provjere tipa 4 milijuna linija Python koda. 1. dio

Dropbox puno piše u Pythonu. To je jezik koji koristimo iznimno široko, i za pozadinske usluge i za klijentske aplikacije za stolna računala. Također dosta koristimo Go, TypeScript i Rust, ali Python je naš glavni jezik. S obzirom na naše razmjere, a riječ je o milijunima linija Python koda, pokazalo se da je dinamičko tipkanje takvog koda nepotrebno kompliciralo njegovo razumijevanje i počelo ozbiljno utjecati na produktivnost rada. Kako bismo ublažili ovaj problem, počeli smo postupno prelaziti naš kod na statičku provjeru tipa koristeći mypy. Ovo je vjerojatno najpopularniji samostalni sustav provjere tipa za Python. Mypy je projekt otvorenog koda, čiji glavni programeri rade u Dropboxu.

Dropbox je bio jedna od prvih tvrtki koja je implementirala statičku provjeru tipa u Python kodu na ovoj razini. Mypy se ovih dana koristi u tisućama projekata. Ovaj alat bezbroj puta, kako kažu, "testiran u bitci". Prevalili smo dug put da stignemo ovdje gdje smo sada. Na tom putu bilo je mnogo neuspješnih pothvata i neuspješnih eksperimenata. Ovaj post pokriva povijest statičke provjere tipa u Pythonu, od njegovih kamenitih početaka kao dijela mog istraživačkog projekta, do danas, kada su provjera tipa i nagovještavanje tipa postali uobičajeni za nebrojene programere koji pišu u Pythonu. Ove mehanizme sada podržavaju mnogi alati kao što su IDE i analizatori koda.

Pročitajte drugi dio

Zašto je potrebna provjera tipa?

Ako ste ikada koristili dinamički tipkani Python, mogli biste biti zbunjeni zašto se u posljednje vrijeme digla tolika buka oko statičkog tipkanja i mypyja. Ili možda volite Python upravo zbog njegovog dinamičnog tipkanja, a ovo što se događa jednostavno vas uzrujava. Ključ vrijednosti statičkog tipkanja je opseg rješenja: što je vaš projekt veći, to više naginjete statičkom tipkanju, i na kraju, to vam je više potrebno.

Pretpostavimo da je određeni projekt dosegao veličinu od nekoliko desetaka tisuća redaka, a ispostavilo se da nekoliko programera radi na njemu. Gledajući sličan projekt, na temelju našeg iskustva, možemo reći da će razumijevanje njegovog koda biti ključno za održavanje produktivnosti programera. Bez napomena tipa može biti teško shvatiti, na primjer, koje argumente proslijediti funkciji ili koje tipove funkcija može vratiti. Ovdje su tipična pitanja na koja je često teško odgovoriti bez upotrebe oznaka tipa:

  • Može li se ova funkcija vratiti None?
  • Što bi trebao biti ovaj argument? items?
  • Što je tip atributa id: int Je li, str, ili možda neki prilagođeni tip?
  • Treba li ovaj argument biti popis? Je li mu moguće proslijediti tuple?

Ako pogledate sljedeći isječak koda s oznakom tipa i pokušate odgovoriti na slična pitanja, ispada da je ovo najjednostavniji zadatak:

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

  • read_metadata ne vraća se None, budući da vrsta povrata nije Optional[…].
  • argument items je niz linija. Ne može se ponavljati nasumično.
  • Atribut id je niz bajtova.

U idealnom svijetu očekivalo bi se da će sve takve suptilnosti biti opisane u ugrađenoj dokumentaciji (docstring). Ali iskustvo daje puno primjera činjenice da se takva dokumentacija često ne poštuje u kodu s kojim morate raditi. Čak i ako takva dokumentacija postoji u kodu, ne može se računati na njegovu apsolutnu ispravnost. Ova dokumentacija može biti nejasna, netočna i otvorena za nesporazume. U velikim timovima ili velikim projektima ovaj problem može postati iznimno akutan.

Dok se Python ističe u ranim ili srednjim fazama projekata, u nekom trenutku uspješni projekti i tvrtke koje koriste Python mogu se suočiti s vitalnim pitanjem: "Trebamo li sve prepisati u statički tipiziranom jeziku?".

Sustavi za provjeru tipa kao što je mypy rješavaju gornji problem dajući programeru formalni jezik za opisivanje tipova i provjerom odgovaraju li tipske deklaracije implementaciji programa (i, izborno, provjerom njihovog postojanja). Općenito, možemo reći da nam ovi sustavi stavljaju na raspolaganje nešto poput pažljivo provjerene dokumentacije.

Korištenje takvih sustava ima i druge prednosti, a one su već potpuno netrivijalne:

  • Sustav provjere tipa može otkriti neke male (i ne tako male) pogreške. Tipičan primjer je kada zaborave obraditi vrijednost None ili neko drugo posebno stanje.
  • Refaktoriranje koda uvelike je pojednostavljeno jer je sustav provjere tipa često vrlo precizan o tome koji kod treba promijeniti. Pritom se ne trebamo nadati 100% pokrivenosti koda testovima, što u svakom slučaju najčešće nije izvedivo. Ne trebamo kopati u dubinu praćenja stoga da bismo otkrili uzrok problema.
  • Čak i na velikim projektima, mypy često može napraviti punu provjeru tipa u djeliću sekunde. A izvođenje testova obično traje nekoliko desetaka sekundi ili čak minuta. Sustav provjere tipa daje programeru trenutnu povratnu informaciju i omogućuje mu brže obavljanje posla. On više ne treba pisati krhke i teške za održavanje jedinične testove koji zamjenjuju stvarne entitete s mockovima i zakrpama samo kako bi brže dobio rezultate testa koda.

IDE-ovi i uređivači kao što su PyCharm ili Visual Studio Code koriste snagu napomena tipa kako bi programerima omogućili dovršavanje koda, označavanje pogrešaka i podršku za često korištene jezične konstrukcije. A ovo su samo neke od prednosti tipkanja. Za neke programere sve je to glavni argument u korist tipkanja. To je nešto što ima koristi odmah nakon implementacije. Ovaj slučaj upotrebe za tipove ne zahtijeva poseban sustav provjere tipa kao što je mypy, iako treba napomenuti da mypy pomaže da bilješke tipa budu dosljedne kodu.

Pozadina mypyja

Povijest mypyja započela je u Velikoj Britaniji, u Cambridgeu, nekoliko godina prije nego što sam se pridružio Dropboxu. Sudjelovao sam, kao dio svog doktorskog istraživanja, u unifikaciji statički tipiziranih i dinamičkih jezika. Inspirirao me članak o inkrementalnom tipkanju Jeremyja Sieka i Walida Tahe te projekt Typed Racket. Pokušao sam pronaći načine za korištenje istog programskog jezika za različite projekte - od malih skripti do baza kodova koje se sastoje od mnogo milijuna redaka. U isto vrijeme, želio sam osigurati da se u projektu bilo kojeg razmjera ne moraju raditi preveliki kompromisi. Važan dio svega ovoga bila je ideja postupnog prelaska s netipiziranog prototipa na sveobuhvatno testiran statički tipizirani gotov proizvod. Danas se te ideje uglavnom uzimaju zdravo za gotovo, no 2010. godine to je bio problem koji se još aktivno istraživao.

Moj izvorni rad na provjeri tipa nije bio usmjeren na Python. Umjesto toga, koristio sam mali "domaći" jezik Alore. Evo primjera koji će vam pomoći da shvatite o čemu govorimo (komentacije tipa ovdje nisu obavezne):

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

Korištenje pojednostavljenog materinjeg jezika uobičajen je pristup koji se koristi u znanstvenim istraživanjima. To je tako, ne samo zato što vam omogućuje brzo provođenje eksperimenata, već i zbog činjenice da se ono što nema nikakve veze s istraživanjem može lako zanemariti. Programski jezici u stvarnom svijetu obično su fenomeni velikih razmjera sa složenim implementacijama, a to usporava eksperimentiranje. Međutim, svi rezultati temeljeni na pojednostavljenom jeziku izgledaju pomalo sumnjivo, budući da je u dobivanju tih rezultata istraživač možda žrtvovao razmatranja važna za praktičnu upotrebu jezika.

Moj alat za provjeru tipa za Alore izgledao je obećavajuće, ali želio sam ga testirati eksperimentirajući sa stvarnim kodom, koji, možete reći, nije napisan u Aloreu. Na moju sreću, jezik Alore se uglavnom temeljio na istim idejama kao Python. Bilo je dovoljno jednostavno promijeniti alat za provjeru tipa tako da može raditi s Pythonovom sintaksom i semantikom. To nam je omogućilo da isprobamo provjeru tipa u Python kodu otvorenog koda. Osim toga, napisao sam transpiler za pretvaranje koda napisanog u Aloreu u Python kod i upotrijebio ga za prijevod svog koda za provjeru tipa. Sada sam imao sustav provjere tipa napisan u Pythonu koji je podržavao podskup Pythona, neku vrstu tog jezika! (Određene arhitektonske odluke koje su imale smisla za Alore bile su loše prikladne za Python, a to je još uvijek vidljivo u dijelovima mypy kodne baze.)

Zapravo, jezik koji podržava moj sustav tipa u ovom se trenutku ne može sasvim nazvati Python: to je bila varijanta Pythona zbog nekih ograničenja sintakse za označavanje tipa Python 3.

Izgledalo je kao mješavina Jave i Pythona:

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

Jedna od mojih ideja u to vrijeme bila je koristiti oznake tipa za poboljšanje performansi kompajliranjem ove vrste Pythona u C ili možda JVM bajt kod. Došao sam do faze pisanja prototipa prevoditelja, ali sam odustao od te ideje, budući da je sama provjera tipa izgledala prilično korisno.

Na kraju sam predstavio svoj projekt na PyConu 2013. u Santa Clari. O tome sam razgovarao i s Guidom van Rossumom, dobronamjernim doživotnim diktatorom Pythona. Uvjerio me da odustanem od vlastite sintakse i držim se standardne sintakse Python 3. Python 3 podržava bilješke funkcija, tako da se moj primjer može prepisati kao što je prikazano u nastavku, što rezultira normalnim Python programom:

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

Morao sam napraviti neke kompromise (prije svega, želim napomenuti da sam izmislio vlastitu sintaksu upravo iz tog razloga). Konkretno, Python 3.3, najnovija verzija jezika u to vrijeme, nije podržavala varijabilne komentare. S Guidom sam e-poštom razgovarao o raznim mogućnostima za sintaktički dizajn takvih zabilješki. Odlučili smo koristiti komentare tipa za varijable. Ovo je poslužilo predviđenoj svrsi, ali je bilo pomalo glomazno (Python 3.6 nam je dao ljepšu sintaksu):

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

Komentari tipa također su bili korisni za podršku Python 2, koji nema ugrađenu podršku za komentare tipa:

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

Ispostavilo se da ovi (i drugi) kompromisi zapravo nisu bili važni - prednosti statičkog tipkanja značile su da su korisnici ubrzo zaboravili na ne baš savršenu sintaksu. Budući da u Python kodu s provjerom tipa nisu korištene posebne sintaktičke konstrukcije, postojeći Python alati i procesi obrade koda nastavili su raditi normalno, što je programerima znatno olakšalo učenje novog alata.

Guido me također uvjerio da se pridružim Dropboxu nakon što sam završio diplomski rad. Tu počinje najzanimljiviji dio mypy priče.

Da bi se nastavio ...

Dragi čitatelji! Ako koristite Python, recite nam o opsegu projekata koje razvijate na ovom jeziku.

Put do provjere tipa 4 milijuna linija Python koda. 1. dio
Put do provjere tipa 4 milijuna linija Python koda. 1. dio

Izvor: www.habr.com

Dodajte komentar