Pot do preverjanja tipa 4 milijonov vrstic kode Python. 1. del

Danes vam predstavljamo prvi del prevoda gradiva o tem, kako se Dropbox ukvarja z nadzorom tipa kode Python.

Pot do preverjanja tipa 4 milijonov vrstic kode Python. 1. del

Dropbox veliko piše v Pythonu. To je jezik, ki ga uporabljamo izjemno široko, tako za zaledne storitve kot za namizne odjemalske aplikacije. Veliko uporabljamo tudi Go, TypeScript in Rust, vendar je Python naš glavni jezik. Glede na naš obseg, govorimo pa o milijonih vrstic kode Python, se je izkazalo, da je dinamično tipkanje takšne kode po nepotrebnem zapletlo njeno razumevanje in začelo resno vplivati ​​na produktivnost dela. Da bi ublažili to težavo, smo začeli postopoma prenašati kodo na statično preverjanje tipa z uporabo mypy. To je verjetno najbolj priljubljen samostojni sistem za preverjanje tipa za Python. Mypy je odprtokodni projekt, njegovi glavni razvijalci delajo v Dropboxu.

Dropbox je bil eno prvih podjetij, ki je uvedlo statično preverjanje tipa v kodi Python v tem obsegu. Mypy se danes uporablja v tisočih projektih. To orodje je neštetokrat, kot pravijo, "preizkušeno v boju." Prehodili smo dolgo pot, da smo prišli tukaj, kjer smo zdaj. Na tej poti je bilo veliko neuspešnih podvigov in neuspelih poskusov. Ta objava pokriva zgodovino statičnega preverjanja tipa v Pythonu, od njegovih skalnih začetkov v okviru mojega raziskovalnega projekta do danes, ko sta preverjanje tipa in namigovanje tipa postala običajna za nešteto razvijalcev, ki pišejo v Pythonu. Te mehanizme zdaj podpirajo številna orodja, kot so IDE in analizatorji kode.

Preberite drugi del

Zakaj je preverjanje tipa potrebno?

Če ste kdaj uporabljali dinamično tipkani Python, ste morda nekoliko zmedeni, zakaj je v zadnjem času tako hrupa okoli statičnega tipkanja in mypyja. Morda pa vam je Python všeč ravno zaradi njegovega dinamičnega tipkanja in vas dogajanje preprosto vznemirja. Ključ do vrednosti statičnega tipkanja je obseg rešitev: večji kot je vaš projekt, bolj se nagibate k statičnemu tipkanju in na koncu ga bolj resnično potrebujete.

Recimo, da je določen projekt dosegel velikost več deset tisoč vrstic in izkazalo se je, da na njem dela več programerjev. Če pogledamo podoben projekt, lahko na podlagi naših izkušenj rečemo, da bo razumevanje njegove kode ključno za ohranjanje produktivnosti razvijalcev. Brez opomb tipa je lahko težko ugotoviti, na primer, katere argumente posredovati funkciji ali katere vrste lahko funkcija vrne. Tukaj so tipična vprašanja, na katera je pogosto težko odgovoriti brez uporabe opomb o vrsti:

  • Ali se lahko ta funkcija vrne None?
  • Kakšen naj bi bil ta argument? items?
  • Kaj je vrsta atributa id: int ali je, str, ali morda kakšna vrsta po meri?
  • Ali naj bo ta argument seznam? Ali mu je mogoče posredovati tuple?

Če pogledate naslednji delček kode z oznako tipa in poskusite odgovoriti na podobna vprašanja, se izkaže, da je to najpreprostejša naloga:

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

  • read_metadata se ne vrne None, ker povratni tip ni Optional[…].
  • Prepir items je zaporedje vrstic. Ni ga mogoče naključno ponoviti.
  • Atribut id je niz bajtov.

V idealnem svetu bi pričakovali, da bodo vse te podrobnosti opisane v vgrajeni dokumentaciji (docstring). Toda izkušnje dajejo veliko primerov dejstva, da takšna dokumentacija pogosto ni upoštevana v kodi, s katero morate delati. Tudi če je takšna dokumentacija v kodi, ni mogoče računati na njeno absolutno pravilnost. Ta dokumentacija je lahko nejasna, netočna in odprta za nesporazume. V velikih timih ali velikih projektih lahko ta problem postane izjemno pereč.

Medtem ko je Python odličen v zgodnjih ali vmesnih fazah projektov, se lahko na neki točki uspešni projekti in podjetja, ki uporabljajo Python, soočijo z bistvenim vprašanjem: "Ali naj vse prepišemo v statično tipiziran jezik?".

Sistemi za preverjanje tipov, kot je mypy, rešujejo zgornjo težavo tako, da razvijalcu zagotovijo formalni jezik za opisovanje tipov in s preverjanjem, ali se deklaracije tipa ujemajo z implementacijo programa (in po želji preverijo njihov obstoj). Na splošno lahko rečemo, da nam ti sistemi dajejo na razpolago nekaj kot skrbno preverjeno dokumentacijo.

Uporaba takšnih sistemov ima še druge prednosti in so že povsem netrivialne:

  • Sistem za preverjanje tipa lahko zazna nekaj majhnih (in ne tako majhnih) napak. Tipičen primer je, ko pozabijo obdelati vrednost None ali kakšno drugo posebno stanje.
  • Preoblikovanje kode je močno poenostavljeno, ker je sistem za preverjanje tipa pogosto zelo natančen glede kode, ki jo je treba spremeniti. Obenem ni treba upati na 100-odstotno pokritost kode s testi, kar pa tako ali tako običajno ni izvedljivo. Da bi ugotovili vzrok težave, se nam ni treba poglobiti v globino sledenja skladov.
  • Tudi pri velikih projektih lahko mypy pogosto opravi popolno preverjanje tipa v delčku sekunde. In izvedba testov običajno traja več deset sekund ali celo minut. Sistem za preverjanje tipa daje programerju takojšnje povratne informacije in mu omogoča, da svoje delo opravi hitreje. Ni mu več treba pisati krhkih testov enot, ki jih je težko vzdrževati, ki nadomeščajo resnične entitete z lažnimi in popravki samo zato, da bi hitreje dobil rezultate testa kode.

IDE in urejevalniki, kot sta PyCharm ali Visual Studio Code, uporabljajo moč opomb tipa, da razvijalcem zagotovijo dokončanje kode, označevanje napak in podporo za pogosto uporabljene jezikovne konstrukcije. In to je le nekaj prednosti tipkanja. Za nekatere programerje je vse to glavni argument v prid tipkanju. To je nekaj, kar koristi takoj po izvedbi. Ta primer uporabe za tipe ne zahteva ločenega sistema za preverjanje tipa, kot je mypy, čeprav je treba opozoriti, da mypy pomaga ohranjati opombe tipa skladne s kodo.

Ozadje mypy

Zgodovina mypy se je začela v Veliki Britaniji, v Cambridgeu, nekaj let preden sem se pridružil Dropboxu. V okviru doktorskega raziskovanja sem se ukvarjal s poenotenjem statično tipiziranih in dinamičnih jezikov. Navdihnil me je članek Jeremyja Sieka in Walida Tahe o postopnem tipkanju ter projekt Typed Racket. Poskušal sem najti načine za uporabo istega programskega jezika za različne projekte - od majhnih skript do baz kode, sestavljenih iz več milijonov vrstic. Hkrati sem želel zagotoviti, da pri projektu kakršnega koli obsega ne bo treba sklepati prevelikih kompromisov. Pomemben del vsega tega je bila zamisel o postopnem prehodu od netipiziranega prototipnega projekta k celovito testiranemu statično tipkanemu končnemu izdelku. Dandanes so te ideje večinoma samoumevne, toda leta 2010 je bil to problem, ki so ga še aktivno raziskovali.

Moje prvotno delo pri preverjanju tipa ni bilo namenjeno Pythonu. Namesto tega sem uporabil majhen "domači" jezik Alore. Tu je primer, ki vam bo pomagal razumeti, o čem govorimo (pripombe o vrsti 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 maternega jezika je pogost pristop, ki se uporablja v znanstvenih raziskavah. To je tako, nenazadnje zato, ker vam omogoča hitro izvajanje poskusov, pa tudi zaradi dejstva, da je tisto, kar nima nobene zveze z raziskavami, zlahka prezreti. Programski jeziki v resničnem svetu so običajno obsežni pojavi s kompleksnimi izvedbami, kar upočasnjuje eksperimentiranje. Vendar so vsi rezultati, ki temeljijo na poenostavljenem jeziku, videti nekoliko sumljivi, saj je raziskovalec pri pridobivanju teh rezultatov morda žrtvoval vidike, pomembne za praktično uporabo jezikov.

Moj preverjevalnik tipov za Alore je bil videti zelo obetaven, vendar sem ga želel preizkusiti z eksperimentiranjem s pravo kodo, za katero bi lahko rekli, da ni bila napisana v Aloreju. Na mojo srečo je jezik Alore v veliki meri temeljil na istih idejah kot Python. Preverjevalnik tipov je bilo dovolj preprosto spremeniti, da je lahko deloval s sintakso in semantiko Pythona. To nam je omogočilo, da poskusimo preverjanje tipa v odprtokodni kodi Python. Poleg tega sem napisal transpiler za pretvorbo kode, napisane v Alore, v kodo Python in ga uporabil za prevajanje moje kode za preverjanje tipov. Zdaj sem imel sistem za preverjanje tipa, napisan v Pythonu, ki je podpiral podmnožico Pythona, neke vrste ta jezik! (Določene arhitekturne odločitve, ki so bile smiselne za Alore, niso bile primerne za Python in to je še vedno opazno v delih kodne baze mypy.)

Pravzaprav jezika, ki ga podpira moj tipski sistem, na tej točki še ni bilo mogoče imenovati Python: bil je različica Pythona zaradi nekaterih omejitev sintakse opomb tipa Python 3.

Videti je bilo kot mešanica Jave in Pythona:

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

Ena od mojih takratnih idej je bila uporaba opomb tipa za izboljšanje zmogljivosti s prevajanjem te vrste Pythona v C ali morda bajtno kodo JVM. Prišel sem do faze pisanja prototipa prevajalnika, vendar sem to zamisel opustil, saj se je samo preverjanje tipa zdelo precej uporabno.

Svoj projekt sem na koncu predstavil na PyCon 2013 v Santa Clari. O tem sem govoril tudi z Guidom van Rossumom, dobrohotnim doživljenjskim diktatorjem Pythona. Prepričal me je, da sem opustil lastno sintakso in se držal standardne sintakse Python 3. Python 3 podpira opombe funkcij, zato bi lahko moj primer prepisali, kot je prikazano spodaj, kar je povzročilo običajen program Python:

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

Moral sem narediti nekaj kompromisov (najprej želim opozoriti, da sem prav zaradi tega izumil svojo sintakso). Zlasti Python 3.3, takrat najnovejša različica jezika, ni podpiral opomb spremenljivk. Z Guidom sem po e-pošti razpravljal o različnih možnostih skladenjskega oblikovanja takšnih opomb. Odločili smo se za uporabo tipskih komentarjev za spremenljivke. To je služilo predvidenemu namenu, vendar je bilo nekoliko okorno (Python 3.6 nam je dal lepšo sintakso):

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

Tipski komentarji so prav tako prišli prav za podporo Python 2, ki nima vgrajene podpore za tipske opombe:

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 pomembni - prednosti statičnega tipkanja so pomenile, da so uporabniki kmalu pozabili na manj kot popolno sintakso. Ker v tipsko preverjeni kodi Python niso bili uporabljeni nobeni posebni sintaktični konstrukti, so obstoječa orodja Python in procesi obdelave kode še naprej delovali normalno, kar je razvijalcem olajšalo učenje novega orodja.

Guido me je tudi prepričal, da sem se pridružil Dropboxu, ko sem zaključila diplomsko nalogo. Tu se začne najbolj zanimiv del mypy zgodbe.

Se nadaljuje ...

Drage bralke in bralci! Če uporabljate Python, nam povejte o obsegu projektov, ki jih razvijate v tem jeziku.

Pot do preverjanja tipa 4 milijonov vrstic kode Python. 1. del
Pot do preverjanja tipa 4 milijonov vrstic kode Python. 1. del

Vir: www.habr.com

Dodaj komentar