4 milijonų Python kodo eilučių tipo tikrinimo kelias. 1 dalis

Šiandien atkreipiame jūsų dėmesį į pirmąją medžiagos vertimo dalį apie tai, kaip „Dropbox“ sprendžia Python kodo tipo valdymą.

4 milijonų Python kodo eilučių tipo tikrinimo kelias. 1 dalis

Dropbox daug rašo Python. Tai kalba, kurią labai plačiai naudojame tiek galinėms paslaugoms, tiek darbalaukio klientų programoms. Taip pat daug naudojame „Go“, „TypeScript“ ir „Rust“, tačiau „Python“ yra pagrindinė mūsų kalba. Atsižvelgiant į mūsų mastą, o mes kalbame apie milijonus Python kodo eilučių, paaiškėjo, kad dinaminis tokio kodo įvedimas be reikalo apsunkino jo supratimą ir pradėjo rimtai paveikti darbo našumą. Norėdami sušvelninti šią problemą, pradėjome palaipsniui pereiti prie savo kodo į statinį tipo tikrinimą naudodami mypy. Tai turbūt pati populiariausia autonominė Python tipo tikrinimo sistema. „Mypy“ yra atvirojo kodo projektas, jo pagrindiniai kūrėjai dirba „Dropbox“.

„Dropbox“ buvo viena iš pirmųjų kompanijų, įdiegusių statinį tipo tikrinimą Python kode tokiu mastu. Šiomis dienomis „Mypy“ naudojama tūkstančiuose projektų. Šis įrankis daugybę kartų, kaip sakoma, „išbandytas mūšyje“. Mes nuėjome ilgą kelią, kad pasiektume ten, kur esame dabar. Pakeliui buvo daug nesėkmingų įmonių ir nesėkmingų eksperimentų. Šis įrašas apima statinio tipo tikrinimo „Python“ programoje istoriją, nuo jos sudėtingų pradžios, kaip mano tyrimo projekto dalies, iki šių dienų, kai tipo tikrinimas ir užuominos apie tipą tapo įprasta daugeliui kūrėjų, rašančių Python. Šiuos mechanizmus dabar palaiko daugelis įrankių, tokių kaip IDE ir kodo analizatoriai.

Perskaitykite antrąją dalį

Kodėl reikalinga tipo patikra?

Jei kada nors naudojote dinamiškai įvedamą Python, gali kilti neaiškumų, kodėl pastaruoju metu kyla toks šurmulys dėl statinio rašymo ir „mypy“. O gal jums patinka Python būtent dėl ​​jo dinamiško spausdinimo, o tai, kas vyksta, jus tiesiog nuliūdina. Raktas į statinio spausdinimo vertę yra sprendimų mastas: kuo didesnis jūsų projektas, tuo labiau linksite į statinį spausdinimą, o galiausiai tuo labiau jums jo reikia.

Tarkime, tam tikras projektas pasiekė dešimčių tūkstančių eilučių dydį ir paaiškėjo, kad prie jo dirba keli programuotojai. Žvelgdami į panašų projektą, remdamiesi savo patirtimi, galime pasakyti, kad jo kodo supratimas bus raktas į kūrėjų produktyvumą. Be tipo anotacijų gali būti sunku išsiaiškinti, pavyzdžiui, kokius argumentus perduoti funkcijai arba kokius tipus funkcija gali grąžinti. Čia pateikiami tipiški klausimai, į kuriuos dažnai sunku atsakyti nenaudojant tipo anotacijų:

  • Ar ši funkcija gali grįžti None?
  • Koks turėtų būti šis argumentas? items?
  • Kas yra atributo tipas id: int ar tai, str, o gal koks individualus tipas?
  • Ar šis argumentas turėtų būti sąrašas? Ar galima jam perduoti eilutę?

Jei pažvelgsite į šį tipo anotuotą kodo fragmentą ir bandysite atsakyti į panašius klausimus, paaiškės, kad tai yra paprasčiausia užduotis:

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

  • read_metadata negrįžta None, nes grąžinimo tipas nėra Optional[…].
  • Argumentas items yra eilučių seka. Jis negali būti kartojamas atsitiktinai.
  • Atributas id yra baitų eilutė.

Idealiame pasaulyje būtų galima tikėtis, kad visos tokios subtilybės bus aprašytos integruotoje dokumentacijoje (docstring). Tačiau patirtis suteikia daug pavyzdžių, kad tokia dokumentacija dažnai nepaisoma kode, su kuriuo turite dirbti. Net jei tokia dokumentacija yra kode, negalima tikėtis absoliutaus jos teisingumo. Šie dokumentai gali būti neaiškūs, netikslūs ir gali būti nesuprantami. Didelėse komandose ar dideliuose projektuose ši problema gali tapti itin opi.

Nors „Python“ puikiai veikia pradiniame ar tarpiniame projektų etapuose, tam tikru momentu sėkmingi projektai ir įmonės, naudojančios „Python“, gali susidurti su esminiu klausimu: „Ar turėtume viską perrašyti statiškai įvesta kalba?“.

Tipo tikrinimo sistemos, tokios kaip mypy, išsprendžia pirmiau minėtą problemą, pateikdamos kūrėjui formalią tipams apibūdinti skirtą kalbą ir patikrindamos, ar tipo deklaracijos atitinka programos įgyvendinimą (ir, pasirinktinai, patikrindamos, ar jos egzistuoja). Apskritai galime teigti, kad šios sistemos suteikia mums kažką panašaus į kruopščiai patikrintą dokumentaciją.

Tokių sistemų naudojimas turi kitų privalumų, ir jie jau yra visiškai nereikšmingi:

  • Tipo tikrinimo sistema gali aptikti kai kurias mažas (ir ne tokias) klaidas. Tipiškas pavyzdys, kai jie pamiršta apdoroti vertę None ar kita ypatinga sąlyga.
  • Kodo pertvarkymas yra labai supaprastintas, nes tipo tikrinimo sistema dažnai labai tiksliai nustato, kokį kodą reikia keisti. Tuo pačiu metu mums nereikia tikėtis 100% kodo aprėpties atliekant testus, o tai bet kuriuo atveju paprastai nėra įmanoma. Mums nereikia gilintis į krūvos pėdsakų gelmes, kad išsiaiškintume problemos priežastį.
  • Net ir didelių projektų atveju mypy dažnai gali atlikti pilną tipo patikrą per sekundės dalį. O testų vykdymas dažniausiai užtrunka keliasdešimt sekundžių ar net minučių. Tipo tikrinimo sistema programuotojui suteikia tiesioginį grįžtamąjį ryšį ir leidžia greičiau atlikti savo darbą. Jam nebereikia rašyti trapių ir sunkiai prižiūrimų vienetų testų, kurie tikrus objektus pakeičia maketais ir pataisomis, kad greičiau gautų kodo testų rezultatus.

IDE ir redaktoriai, tokie kaip „PyCharm“ arba „Visual Studio Code“, naudoja tipo anotacijų galią, kad suteiktų kūrėjams kodo užbaigimo, klaidų paryškinimo ir dažniausiai naudojamų kalbos konstrukcijų palaikymą. Ir tai tik dalis spausdinimo pranašumų. Kai kuriems programuotojams visa tai yra pagrindinis argumentas spausdinimo naudai. Tai naudinga iškart po įgyvendinimo. Šis tipų naudojimo atvejis nereikalauja atskiros tipo tikrinimo sistemos, pvz., mypy, nors reikia pažymėti, kad mypy padeda išlaikyti tipo anotacijas suderintas su kodu.

Mypy fonas

Mypy istorija prasidėjo JK, Kembridže, likus keleriems metams iki man prisijungiant prie Dropbox. Vykdydamas doktorantūros studijas dalyvavau statiškai įvestų ir dinamiškų kalbų unifikavime. Mane įkvėpė Jeremy Siek ir Walid Taha straipsnis apie laipsnišką spausdinimą ir projektas Typed Racket. Bandžiau rasti būdų, kaip tą pačią programavimo kalbą panaudoti įvairiems projektams – nuo ​​mažų scenarijų iki kodų bazių, susidedančių iš daugybės milijonų eilučių. Kartu norėjau užtikrinti, kad bet kokio masto projekte nereikėtų daryti per didelių kompromisų. Svarbi viso to dalis buvo idėja palaipsniui pereiti nuo netipuoto prototipo projekto prie visapusiškai išbandyto statiškai spausdinamo galutinio produkto. Šiais laikais šios idėjos iš esmės laikomos savaime suprantamais, tačiau 2010 metais tai buvo problema, kuri vis dar buvo aktyviai gvildenama.

Mano pradinis darbas tipo tikrinimo srityje nebuvo skirtas Python. Vietoje to naudojau nedidelę „naminę“ kalbą Alore. Štai pavyzdys, kuris leis suprasti, apie ką mes kalbame (tipo komentarai čia neprivalomi):

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

Supaprastintos gimtosios kalbos naudojimas yra įprastas metodas, naudojamas moksliniuose tyrimuose. Taip yra ne tik todėl, kad tai leidžia greitai atlikti eksperimentus, bet ir dėl to, kad tai, kas neturi nieko bendro su tyrimu, gali būti lengvai ignoruojama. Realaus pasaulio programavimo kalbos paprastai yra didelio masto reiškiniai su sudėtingais įgyvendinimais, o tai lėtina eksperimentavimą. Tačiau bet kokie rezultatai, pagrįsti supaprastinta kalba, atrodo šiek tiek įtartinai, nes gaudamas šiuos rezultatus tyrėjas galėjo paaukoti svarstymus, svarbius praktiniam kalbų vartojimui.

Mano Alore tipo tikrintuvas atrodė daug žadantis, bet norėjau jį išbandyti eksperimentuodamas su tikru kodu, kuris, galima sakyti, nebuvo parašytas Alore. Mano laimei, Alore kalba daugiausia buvo pagrįsta tomis pačiomis idėjomis kaip ir Python. Pakankamai lengva buvo perdaryti tipo tikrintuvą, kad jis veiktų su Python sintaksė ir semantika. Tai leido mums išbandyti tipo tikrinimą atvirojo kodo Python kode. Be to, parašiau transpilerį, skirtą Alore parašytui kodui konvertuoti į Python kodą, ir panaudojau jį savo tipo tikrintuvo kodui išversti. Dabar aš turėjau tipo tikrinimo sistemą, parašytą Python, kuri palaikė Python poaibį, tam tikrą tos kalbos pogrupį! (Kai kurie architektūriniai sprendimai, kurie buvo prasmingi Alore, buvo prastai pritaikyti Python, ir tai vis dar pastebima kai kuriose mypy kodų bazės dalyse.)

Tiesą sakant, mano tipo sistemos palaikoma kalba šiuo metu negali būti vadinama Python: tai buvo Python variantas dėl kai kurių Python 3 tipo anotacijų sintaksės apribojimų.

Tai atrodė kaip Java ir Python mišinys:

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

Viena iš mano idėjų tuo metu buvo naudoti tipo anotacijas, siekiant pagerinti našumą, kompiliuojant tokio tipo Python į C arba galbūt JVM baitinį kodą. Aš patekau į kompiliatoriaus prototipo rašymo etapą, bet atsisakiau šios idėjos, nes pats tipo tikrinimas atrodė gana naudingas.

Galų gale pristačiau savo projektą PyCon 2013 Santa Klaroje. Taip pat apie tai kalbėjausi su Guido van Rossum, geranorišku Python diktatoriumi visam gyvenimui. Jis įtikino mane atsisakyti savo sintaksės ir laikytis standartinės Python 3 sintaksės. Python 3 palaiko funkcijų anotacijas, todėl mano pavyzdį galima perrašyti taip, kaip parodyta toliau, ir gauti normalią Python programą:

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

Teko daryti tam tikrus kompromisus (visų pirma, noriu pažymėti, kad būtent dėl ​​šios priežasties sugalvojau savo sintaksę). Visų pirma, Python 3.3, tuo metu naujausia kalbos versija, nepalaikė kintamųjų komentarų. Su Gvidu el. paštu aptariau įvairias tokių anotacijų sintaksinio dizaino galimybes. Nusprendėme kintamiesiems naudoti tipo komentarus. Tai atitiko numatytą tikslą, bet buvo šiek tiek sudėtinga (Python 3.6 suteikė mums gražesnę sintaksę):

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

Tipo komentarai taip pat buvo naudingi palaikant Python 2, kuri neturi integruoto tipo komentarų palaikymo:

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

Paaiškėjo, kad šie (ir kiti) kompromisai nelabai svarbūs – dėl statinio rašymo pranašumų vartotojai greitai pamiršo ne tokią idealią sintaksę. Kadangi patikrintame Python kode nebuvo naudojamos specialios sintaksės konstrukcijos, esami Python įrankiai ir kodo apdorojimo procesai ir toliau veikė įprastai, todėl kūrėjams buvo daug lengviau išmokti naują įrankį.

Gvidas taip pat įtikino mane prisijungti prie „Dropbox“, kai baigiau baigiamąjį darbą. Čia ir prasideda įdomiausia slaptos istorijos dalis.

Turi būti tęsiama ...

Mieli skaitytojai! Jei naudojate Python, papasakokite apie projektų, kuriuos kuriate šia kalba, mastą.

4 milijonų Python kodo eilučių tipo tikrinimo kelias. 1 dalis
4 milijonų Python kodo eilučių tipo tikrinimo kelias. 1 dalis

Šaltinis: www.habr.com

Добавить комментарий