Pot do preverjanja tipov 4 milijonov vrstic Python kode. Del 1

Danes vam predstavljamo prvi del prevoda gradiva o tem, kako v Dropboxu obvladujejo tipe Python kode.

Pot do preverjanja tipov 4 milijonov vrstic Python kode. Del 1

V Dropboxu veliko pišemo v Pythonu. To je jezik, ki ga izjemno široko uporabljamo – tako za strežniške storitve kot tudi za namizne odjemalske aplikacije. Poleg tega v velikih količinah uporabljamo Go, TypeScript in Rust, vendar je Python naš glavni jezik. Glede na naše obsege, ki vključujejo milijone vrstic Python kode, se je izkazalo, da je dinamična tipizacija te kode nerazumljivo otežila njeno razumevanje in začela resno vplivati na produktivnost dela. Da bi omilili ta problem, smo začeli postopno prevajati našo kodo na statično preverjanje tipov z uporabo mypy. To je verjetno najbolj priljubljen samostojen sistem preverjanja tipov za Python. Mypy je projekt z odprto kodo, njegovi glavni razvijalci delajo v Dropboxu.

Dropbox je bil ena prvih podjetij, ki je uvedla statično preverjanje tipov v Python kodi v podobnem obsegu. Danes se mypy uporablja v tisočih projektih. Ta orodje je bilo neštetokrat, tako rekoč, 'preverjeno v boji'. Da bi prišli tja, kjer smo zdaj, smo morali prehoditi dolgo pot. Na tej poti je bilo veliko neuspešnih poskusov in neuspešnih eksperimentov. To gradivo pripoveduje o zgodovini statičnega preverjanja tipov v Pythonu – od samega njegovega zahtevnega začetka, ki je bil del mojega raziskovalnega projekta, do danes, ko so preverjanja tipov in namigi za tipe postali običajni za nešteto razvijalcev, ki pišejo v Pythonu. Ti mehanizmi zdaj podpirajo številna orodja – kot so IDE in analize kode.

→ Preberite drugi del

Zakaj je potrebno preverjanje tipov?

Če ste kdaj uporabljali dinamično tipiziran Python, se lahko sprašujete, zakaj je v zadnjem času toliko hrupa okoli statične tipizacije in mypy. Morda vam je Python prav zaradi njegove dinamične tipizacije všeč, kar vas morda vse skupaj le moti. Ključ do vrednosti statične tipizacije je obseg rešitev: čim večji je vaš projekt, tem bolj verjetno se nagibate k statični tipizaciji, in na koncu se boste zavedeli, kako zelo to potrebujete.

Recimo, da je nek projekt dosegel velikost več deset tisoč vrstic in da nanj dela več programerjev. Ko gledamo takšen projekt, lahko na podlagi naših izkušenj trdimo, da je razumevanje njegove kode ključno za ohranjanje produktivnosti razvijalcev. Brez anotacij tipov je včasih težko ugotoviti, na primer, katere argumente je treba posredovati funkciji ali pa katere vrste lahko funkcija vrne. Tukaj je nekaj tipičnih vprašanj, na katera pogosto ni lahko odgovoriti brez uporabe anotacij tipov:

  • Lahko ta funkcija vrne Noben?
  • Kaj bi moral biti ta argument items?
  • Kakšna je vrsta atributo id: int je to, str, ali morda kakšna uporabniška vrsta?
  • Ali mora biti ta argument seznam? Ali lahko vanj posredujemo tuple?

Če pogledamo naslednji del kode, opremljen z anotacijami tipov, in poskusimo odgovoriti na taka vprašanja, se izkaže, da je to preprosta naloga:

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

  • read_metadata ne vrne Noben, ker vrnjena vrsta ni Optional[…].
  • Argument items — to je zaporedje nizov. Ne moremo ga iterirati v poljubnem vrstnem redu.
  • Atribut id — to je vrstica bajtov.

V idealnem svetu bi lahko pričakovali, da bodo vse takšne podrobnosti opisane v vgrajeni dokumentaciji (docstring). Vendar pa izkušnje ponujajo številne primere, da take dokumentacije v kodi, s katero se ukvarjamo, pogosto ni. Tudi če takšna dokumentacija v kodi obstaja, ne moremo računati na njeno absolutno pravilnost. Ta dokumentacija je lahko nejasna, netočna in pušča veliko možnosti za napačno razumevanje. V velikih ekipah ali v velikih projektih lahko ta težava postane zelo izrazita.

Čeprav se Python odlično izkaže v zgodnjih ali vmesnih fazah projektov, se lahko v določenem trenutku uspešni projekti in podjetja, ki uporabljajo Python, srečajo z življenjskim vprašanjem: "Ali moramo vse prepisati v statično tipiziranem jeziku?"

Sistemi za preverjanje tipov, kot je mypy, rešujejo zgoraj omenjeno težavo, saj razvijalcem zagotavljajo formalni jezik za opisovanje tipov ter preverjajo, da opisi tipov ustrezajo izvedbi programov (in, kar ni nujno, preverjajo njihovo obstojnost). Na splošno lahko rečemo, da ti sistemi dajejo nekaj podobnega natančno preverjeni dokumentaciji.

Uporaba takih sistemov prinaša tudi druge prednosti, ki niso zanemarljive:

  • Sistem za preverjanje tipov lahko odkrije nekatere majhne (prav tako pa tudi ne tako majhne) napake. Tipičen primer je, ko pozabimo obdelati vrednost Noben ali kakšno drugo posebno stanje.
  • Refaktoring kode je bistveno poenostavljen, saj sistem za preverjanje tipov pogosto zelo natančno pove, kateri del kode je treba spremeniti. Pri tem nam ni potrebno zanašati se na 100% pokritost kode s testi, kar je v vsakem primeru običajno neizvedljivo. Ni nam treba raziskovati globin poročil o sledenju sklada, da bi ugotovili vzrok težave.
  • Tudi pri velikih projektih mypy pogosto lahko izvede popolno preverjanje tipov v delčku sekunde. Izvajanje testov pa običajno traja desetine sekund ali celo minute. Sistem preverjanja tipov programerju ponuja takojšnje povratne informacije in mu omogoča hitrejše opravljanje svojega dela. Ni več treba pisati krhkih in težko vzdrževanih modulskih testov, ki nadomeščajo dejanske entitete z mokami in popravki samo zato, da bi hitreje dobili rezultate testiranja kode.

IDE-ji in urejevalniki, kot sta PyCharm ali Visual Studio Code, izkoriščajo zmožnosti tipizacije za omogočanje avtomatskega dopolnjevanja kode, označevanja napak in podporo pogosto uporabljenim jezikovnim konstrukcijam. To je le nekaj prednosti, ki jih prinaša tipizacija. Za nekatere programerje so vse to glavni argumenti v podporo tipizaciji. To je tisto, kar prinaša koristi takoj po uvedbi v delo. Ta način uporabe tipov ne zahteva ločenega sistema za preverjanje tipov, kot je mypy, čeprav je treba poudariti, da mypy pomaga pri ohranjanju skladnosti med tipi in kodo.

Zgodovina mypy

Zgodovina mypy se je začela v Veliki Britaniji, v Cambridgeu, nekaj let preden sem se pridružil Dropboxu. V okviru svojega doktorska raziskovalnega dela sem raziskoval vprašanje unifikacije statično tipiziranih in dinamičnih jezikov. Inspiroval me je članek o postopni tipizaciji Jeremyja Sieka in Valida Taha, prav tako pa projekt Typed Racket. Poskušal sem najti načine, kako uporabljati isti programski jezik za različne projekte — od majhnih skript do kode, ki obsega milijone vrstic. Pri tem sem želel, da v projektih katerekoli velikosti ni treba delati prekomernih kompromisov. Pomemben del vsega tega je bila ideja postopnega prehoda od netipiziranega prototipa projekta do temeljito testiranega statično tipiziranega končnega izdelka. Danes so te ideje v veliki meri že sprejete, toda leta 2010 je to bila težava, ki jo je bilo še vedno aktivno raziskovati.

Moje prvotno delo na področju preverjanja tipov ni bilo usmerjeno v Python. Namesto njega sem uporabljal majhen 'domač' jezik. Alore. Tukaj je primer, ki vam bo pomagal razumeti, o čem govorimo (tipne oznake tukaj niso obvezne):

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

Uporaba poenostavljenega jezika, ki je lastne zasnove, je običajen pristop v znanstvenih raziskavah. To je tako predvsem zato, ker omogoča hitro izvajanje poskusov, prav tako pa omogoča, da je mogoče brez težav ignorirati tisto, kar ni povezano z raziskavo. Dejansko uporabljeni programski jeziki so običajno razsežne entitete s kompleksnimi implementacijami, kar upočasni eksperimentiranje. Vendar pa so vsi rezultati, ki temeljijo na poenostavljenem jeziku, videti nekoliko sumljivi, saj je raziskovalec morda ob pridobivanju teh rezultatov naredil kompromis pri pomembnih vidikih praktične uporabe jezikov.

Moje orodje za preverjanje tipov za Alore se je zdelo obetavno, vendar sem ga želel preveriti z izvajanjem poskusov z resnično kodo, katere na Alore dejansko ni bilo. Na srečo je bil jezik Alore do določene mere temeljen na istih idejah kot Python. Z zadostnim trudom je bilo enostavno prilagoditi orodje za preverjanje tipov, da je delovalo s sintakso in semantiko Pythona. To mi je omogočilo preizkušanje preverjanja tipov v odprtokodni Python kodi. Poleg tega sem napisal transpajler za pretvorbo kode, napisane v Alore, v Python kodo in ga uporabil za prevod kode mojega orodja za preverjanje tipov. Zdaj sem imel sistem za preverjanje tipov, napisan v Pythonu, ki je podpiral podmnožico Pythona, nekakšno različico tega jezika! (Nekatere arhitekturne odločitve, ki so imele smisel za Alore, so se slabo uklonile Pythu, kar je še vedno opazno v nekaterih delih kode mypy.)

Dejansko v tem trenutku ni bilo mogoče jezika, ki ga podpira moj sistem tipov, imenovati Python: to je bila različica Pythona z nekaterimi omejitvami sintakse tipnih oznak Pythona 3.

Izgledalo je kot mešanica Jave in Pythona:

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

Ena iz mojih idej takrat je bila uporabljati tipizacijo za izboljšanje zmogljivosti s prevodom te vrste Pythona v C ali morda v bajtno kodo JVM. Napredoval sem do faze pisanja prototipa prevajalnika, vendar sem to idejo opustil, ker se je zdelo, da je sama tipizacija dovolj uporabna.

Na koncu sem svoj projekt predstavil na konferenci PyCon 2013 v SantaClari. O tem sem se pogovarjal tudi z Guidom van Rossumom, velikodušnim doživetim diktatorjem Pythona. Prepričal me je, da opustim svojo sintakso in se držim standardne sintakse Pythona 3. Python 3 podpira tipizacijo funkcij, zato se je moj primer lahko prepisal, kot je prikazano spodaj, tako da je bila to običajna Python program:

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

Moral sem sprejeti nekatere kompromise (najprej naj omenim, da sem zato izumil svojo sintakso). Zlasti Python 3.3, najnovejša različica jezika takrat, ni podpirala tipizacije spremenljivk. Z Guidom sem po elektronski pošti razpravljal o različnih možnostih sintaktične ureditve takšnih tipizacij. Odločili smo se uporabiti komentarje s tipom za spremenljivke. To je omogočilo dosego cilja, vendar je izgledalo nekoliko nerodno (Python 3.6 nam je dal prijetnejšo sintakso):

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

Komentarji s tipi so prav tako koristili za podporo Pythona 2, ki nima vgrajene podpore za tipizacijo:

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

Izkazalo se je, da ti (in drugi) kompromisi v resnici niso bili tako pomembni — prednosti statične tipizacije so pripeljale do tega, da so uporabniki kmalu pozabili na nekoliko neprijetno sintakso. Ker v Python kodi, ki je imela nadzor nad tipi, niso bile uporabljene posebne sintaktične konstrukcije, so obstoječa orodja Python in procesi za obdelavo kode še naprej delovali normalno, kar je znatno olajšalo razvijalcem usvajanje novega orodja.

Guido me je prav tako prepričal, da se pridružim Dropboxu po tem, ko sem zagovarjal svoje diplomsko delo. Tu se začne najbolj zanimiv del zgodbe o mypy.

Nastaviće se…

Spoštovani bralci! Če uporabljate Python, vas prosimo, da nam poveste, o kakšnih projektih delate v tem jeziku.

Pot do preverjanja tipov 4 milijonov vrstic Python kode. Del 1
Pot do preverjanja tipov 4 milijonov vrstic Python kode. Del 1

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster