Put do provjere tipova 4 milijuna linija Python koda. Dio 1

Danas vam predstavljamo prvi dio prijevoda materijala o tome kako u Dropboxu provode kontrolu tipova Python koda.

Put do provjere tipova 4 milijuna linija Python koda. Dio 1

U Dropboxu se puno piše na Pythonu. To je jezik koji koristimo izuzetno široko — kako za backend servise, tako i za desktop klijentske aplikacije. Također u velikim količinama koristimo Go, TypeScript i Rust, ali Python je naš glavni jezik. Uzimajući u obzir naše razmjere, a radi se o milijunima redaka Python koda, pokazalo se da je dinamičko tipiziranje takvog koda neopravdano otežalo njegovo razumijevanje i počelo ozbiljno utjecati na produktivnost. Kako bismo ublažili ovaj problem, počeli smo postepeno prevoditi naš kod na statičku provjeru tipova koristeći mypy. To je vjerojatno najpopularniji samostalni sustav provjere tipova za Python. Mypy je open-source projekt, a njegovi glavni programeri rade u Dropboxu.

Dropbox je jedna od prvih tvrtki koja je implementirala statičku provjeru tipova u Python kodu u sličnom opsegu. Danas se mypy koristi u tisućama projekata. Ovaj alat je nebrojeno puta, kako se kaže, "provjeren u borbi". Da bismo stigli do točke gdje se sada nalazimo, prošli smo dug put. Na tom putu bilo je mnogo neuspješnih pokušaja i neuspjelih eksperimenata. Ovaj materijal pripovijeda o povijesti statičke provjere tipova u Pythonu — od njegovog teškog početka, koji je bio dio mog znanstvenog istraživačkog projekta, do današnjih dana, kada su provjere tipova i sugestije za tipove postali uobičajeni za bezbroj programera koji pišu na Pythonu. Ovi mehanizmi sada su podržani brojnim alatima — poput IDE-a i analizatora koda.

→ Pročitajte drugi dio

Zašto je potrebna provjera tipova?

Ako ste ikada koristili dinamički tipizirani Python, mogli biste imati određenu konfuziju oko toga zašto je u posljednje vrijeme podignuta tolika prašina oko statičke tipizacije i mypy-a. Možda vam se Python sviđa upravo zbog njegove dinamičke tipizacije, a ono što se događa vas jednostavno frustrira. Ključ vrijednosti statičke tipizacije leži u razmjeru rješenja: što je vaš projekt veći, to više skrećete prema statičkoj tipizaciji i, na kraju, to vam je zaista potrebnije.

Pretpostavimo da je neki projekt dosegao veličinu od desetaka tisuća linija i da na njemu rade nekoliko programera. Gledajući takav projekt, možemo reći iz našeg iskustva da je razumijevanje njegovog koda ključno za održavanje produktivnosti programera. Bez tipnih anotacija, teško je na primjer otkriti koje argumente treba proslijediti funkciji ili koje tipove podataka može vratiti neka funkcija. Evo tipičnih pitanja na koja je često teško odgovoriti bez korištenja tipnih anotacija:

  • Može li ova funkcija vratiti Nema?
  • Što bi trebao biti ovaj argument items?
  • Koji je tip atributa id: int je li to, str, ili možda neki korisnički tip?
  • Treba li ovaj argument biti lista? Može li se u njega proslijediti tupla?

Ako pogledamo sljedeći komad koda s tipnim anotacijama i pokušamo odgovoriti na slična pitanja, shvatit ćemo da je to vrlo jednostavan zadatak:

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

  • read_metadata ne vraća Nema, jer vraćeni tip nije Optional[…].
  • Argument items — to je sekvenca nizova. Ne može se iterirati u proizvoljnom redoslijedu.
  • Atribut id — to je niz bajtova.

U idealnom svijetu mogli bismo očekivati da će sve takve fine detalje opisivati ugrađena dokumentacija (docstring). No iskustvo donosi mnogo primjera da takva dokumentacija u kodu s kojim radimo često izostaje. Čak i kada takva dokumentacija postoji, ne možemo se osloniti na njenu apsolutnu točnost. Ova dokumentacija može biti nejasna, netočna, te ostavljati mnogo prostora za pogrešno razumijevanje. U velikim timovima ili na velikim projektima ovaj problem može postati izrazito ozbiljan.

Iako Python pokazuje odlične rezultate u ranim ili srednjim fazama projekata, u određenom trenutku uspješni projekti i tvrtke koje koriste Python mogu se suočiti s životno važnim pitanjem: „Trebamo li sve prepisati na jeziku s statički definiranim tipovima?“

Sustavi provjere tipova poput mypy rješavaju navedeni problem jer programerima pružaju formalni jezik za opisivanje tipova i provjeravaju da opisi tipova odgovaraju implementacijama programa (i, što nije nužno, provjeravaju njihovo postojanje). Općenito, može se reći da ovi sustavi nude nešto poput pažljivo provjerene dokumentacije.

Primjena ovakvih sustava ima i druge prednosti, koje su već potpuno značajne:

  • Sustav provjere tipova može otkriti neke sitne (kao i prilično velike) pogreške. Tipičan primjer je kada zaboravite obraditi vrijednost Nema ili neko drugo posebno stanje.
  • Bitno se olakšava refaktoriranje koda, jer sustav provjere tipova često vrlo precizno ukazuje na to koji dio koda treba izmijeniti. Pri tome nam ne treba ovisiti o 100% pokrivenosti koda testovima, što je ionako obično neizvedivo. Ne moramo proučavati dubine izvještaja o tragovima stoga kako bismo otkrili uzrok kvara.
  • Čak i u velikim projektima mypy često može izvršiti potpunu provjeru tipova u djelićima sekunde. A izvršavanje testova obično traje desetke sekundi ili čak minute. Sustav provjere tipova daje programerima trenutnu povratnu informaciju i omogućuje im da brže obave svoj posao. Više im nije potrebno pisati krhke i teške za održavanje modularne testove koji zamjenjuju stvarne entitete lažnim i patchovima samo da bi brže dobili rezultate testiranja koda.

IDE-ovi i uređivači poput PyCharma ili Visual Studio Code koriste mogućnosti anotacija tipova za pružanje programerima opcija automatskog dovršavanja koda, za označavanje grešaka, za podršku često korištenim jezičnim konstrukcijama. I to je samo neki od plusa koje tipizacija donosi. Za neke programere, sve to predstavlja glavni argument u korist tipizacije. To je nešto što donosi koristi odmah nakon implementacije. Ova upotreba tipova ne zahtijeva primjenu zasebnog sustava provjere tipova, poput mypy-a, iako treba napomenuti da mypy pomaže u održavanju usklađenosti anotacija tipa i koda.

Povijest mypy

Povijest mypy počinje u Ujedinjenom Kraljevstvu, u Cambridgeu, nekoliko godina prije nego što sam se pridružio Dropbox-u. Bavio sam se, u okviru svog doktorskog istraživanja, pitanjem unifikacije statički tipiziranih i dinamičkih jezika. Inspirisala me je članak o postupnoj tipizaciji Jeremyja Sieka i Valida Taha, kao i projekt Typed Racket. Pokušavao sam pronaći načine korištenja istog programskog jezika za razne projekte — od malih skripti do kodnih baza koje se sastoje od mnogo milijuna linija. Pri tome sam želio da ne bude potrebna prevelika kompromitacija, bez obzira na veličinu projekta. Važan dio svega toga bila je ideja o postepenom prelasku od netipiziranog prototipa projekta do temeljito testiranog proizvoda s statičkom tipizacijom. U današnje vrijeme, te ideje se u velikoj mjeri smatraju samorazumljivima, ali 2010. godine to je bio problem koji se još uvijek aktivno istraživao.

Moj prvotni rad u području provjere tipova nije bio usmjeren na Python. Umjesto toga, koristio sam mali "uradak" jezik AloreEvo primjera koji će vam pomoći da shvatite o čemu se radi (annotacije tipova 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 jezika vlastitog razvoja uobičajeni je pristup u znanstvenim istraživanjima. To se, između ostalog, događa zato što takvo nešto omogućava brzo izvođenje eksperimenata, kao i zbog toga što se stvari koje nemaju veze s istraživanjem mogu slobodno zanemariti. Pravi programski jezici obično predstavljaju opsežne pojave sa složenim implementacijama, što usporava eksperimentiranje. Međutim, svi rezultati temeljeni na pojednostavljenom jeziku izgledaju pomalo sumnjivo, jer prilikom postizanja tih rezultata istraživač možda žrtvuje važna razmatranja koja su bitna za praktičnu upotrebu jezika.

Moj alat za provjeru tipova za Alore izgledao je vrlo obećavajuće, ali htio sam ga testirati izvođenjem eksperimenata s pravim kodom, kojeg, može se reći, na Alore nije bilo. Na moju sreću, jezik Alore u velikoj je mjeri bio temeljen na istim idejama kao i Python. Bilo je dovoljno jednostavno prepraviti alat za provjeru tipova da bi mogao raditi s sintaksom i semantikom Pythona. To mi je omogućilo probati provjeru tipova u open-source Python kodu. Osim toga, napisao sam transpajler za prevođenje koda napisanog na Alore u Python kod i iskoristio ga za prevođenje koda mog alata za provjeru tipova. Sada sam imao sistem za provjeru tipova napisan na Pythonu koji je podržavao podskup Pythona, neku vrstu ovog jezika! (Određena arhitektonska rješenja koja su imala smisla za Alore, loše su odgovarala Pythonu, što se i dalje primjećuje u nekim dijelovima kodne baze mypy.)

U stvarnosti, jezik podržan mojim sustavom tipova u tom trenutku nije se mogao potpuno nazvati Pythonom: bio je to varijanta Pythona zbog određenih ograničenja sintakse annotacija tipova Pythona 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 anotacije tipova za poboljšanje performansi prevođenjem ove vrste Pythona u C, ili možda u JVM bytecode. Napredovao sam do faze pisanja prototipa prevoditelja, ali sam odustao od te ideje jer se činilo da je provjera tipova sama po sebi dovoljno korisna.

Na kraju sam predstavio svoj projekt na konferenciji PyCon 2013 u Santa Clari. Također sam o tome razgovarao s Guidom van Rossumom, velikodušnim doživotnim diktatorom Pythona. On me uvjerio da odustanem od vlastite sintakse i pridržavam se standardne sintakse Pythona 3. Python 3 podržava anotacije funkcija, kao rezultat, moj primjer mogao je biti prepisan na sljedeći način, čime je postao normalan Python program:

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

Morao sam ići na neke kompromise (prvo želim napomenuti da sam izumio vlastitu sintaksu upravo iz tog razloga). Naime, Python 3.3, najnovija verzija jezika u to vrijeme, nije podržavala anotacije varijabli. Razgovarao sam s Guidom putem e-pošte o različitim mogućnostima sintaktičkog oblikovanja takvih anotacija. Odlučili smo koristiti komentare s tipovima za varijable. To je omogućilo postizanje cilja, ali je izgledalo pomalo nezgrapno (Python 3.6 nam je pružio ugodniju sintaksu):

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

Komentari s tipovima također su bili korisni za podršku Pythonu 2, koji nema ugrađenu podršku za anotacije tipova:

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 imali veliku važnost — prednosti statičke tipizacije dovele su do toga da su korisnici ubrzo zaboravili na ne posve savršenu sintaksu. S obzirom na to da se sada u Python kodu, u kojem su kontrolirani tipovi, nisu upotrebljavale posebne sintaktičke konstrukcije, postojeći alati i procesi za obradu Pythona nastavili su normalno funkcionirati, što je znatno olakšalo usvajanje novog alata od strane programera.

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

Nastavak slijedi…

Dragi čitatelji! Ako koristite Python, molimo vas da nam kažete o projektima koje razvijate na ovom jeziku, bilo koje veličine.

Put do provjere tipova 4 milijuna linija Python koda. Dio 1
Put do provjere tipova 4 milijuna linija Python koda. Dio 1

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster