Cesta k overeniu typov 4 miliónov riadkov kódu v Pythone. Časť 1

Dnes vám ponúkame prvú časť prekladu materiálu o tom, ako v Dropboxe kontrolujú typy Python kódu.

Cesta k overeniu typov 4 miliónov riadkov kódu v Pythone. Časť 1

V Dropboxe sa veľa píše v jazyku Python. Je to jazyk, ktorý používame veľmi široko — ako pre backendové služby, tak aj pre desktopové klientské aplikácie. Tiež v značnej miere používame Go, TypeScript a Rust, ale Python je naším hlavným jazykom. Ak zohľadníme naše rozmery, ide o milióny riadkov Python kódu, zistili sme, že dynamická typizácia takého kódu neoprávnene sťažuje jeho pochopenie a začína vážne vplývať na produktivitu práce. Na zmiernenie tohto problémy sme začali postupne prevádzať náš kód na statickú kontrolu typov pomocou mypy. To je pravdepodobne najpopulárnejší samostatný systém na kontrolu typov pre Python. Mypy je open-source projekt, jeho hlavní vývojári pracujú v Dropboxe.

Dropbox sa stal jednou z prvých spoločností, ktorá zaviedla statickú kontrolu typov v Python kóde v podobnom rozsahu. Dnes sa mypy používa v tisíckach projektov. Tento nástroj bol nespočetnekrát, povedzme, „overený v boji“. Aby sme sa dostali tam, kde sme teraz, museli sme prejsť dlhú cestu. Na tejto ceste bolo niekoľko neúspešných pokusov a neúspešných experimentov. Tento materiál rozpráva príbeh statickej kontroly typov v Pythone — od jej ťažkého začiatku, ktorý bol súčasťou môjho vedeckého výskumného projektu, až po dnešok, keď sa kontroly typov a poradenstvo o typoch stali bežnými pre nespočetné množstvo vývojárov, ktorí píšu v Pythone. Tieto mechanizmy teraz podporuje množstvo nástrojov — ako sú IDE a analyzátory kódu.

→ Prečítajte si druhú časť

Prečo je potrebná kontrola typov?

Ak ste niekedy používali dynamicky typovaný Python, môžete mať určité nejasnosti ohľadom toho, prečo sa okolo statickej typizácie a mypy v poslednej dobe viedlo toľko diskusií. Možno sa vám Python páči práve kvôli jeho dynamickej typizácii a to, čo sa deje, vás jednoducho rozrušuje. Kľúč k hodnote statickej typizácie spočíva v rozsahu riešení: čím väčší je váš projekt, tým viac sa prikláňate k statickej typizácii a nakoniec tým viac to skutočne potrebujete.

Predpokladajme, že nejaký projekt dosiahol rozsah desiatok tisíc riadkov a zistilo sa, že na ňom pracuje niekoľko programátorov. Pri posudzovaní takého projektu môžeme na základe našich skúseností povedať, že porozumenie jeho kódu sa stane kľúčom k udržaniu produktivity vývojárov. Bez anotácií typov je ťažké zistiť, aké argumenty by mali byť odovzdané funkcii, alebo aké typy hodnôt môže nejaká funkcia vracať. Tu sú typické otázky, na ktoré často nie je jednoduché odpovedať bez použitia anotácií typov:

  • Môže táto funkcia vrátiť None?
  • Čím by mal byť tento argument? items?
  • Aký je typ atribútu id: int je to str, alebo môže byť nejaký používateľský typ?
  • Mal by tento argument byť zoznamom? Môže byť odovzdaný aj ako n-tica?

Keď sa pozriete na nasledujúci útržok kódu s anotáciami typov a pokúsite sa odpovedať na podobné otázky, zistíte, že je to veľmi jednoduchá úloha:

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

  • read_metadata nevracia None, pretože vracaný typ nie je Optional[…].
  • Argument items — je to sekvencia reťazcov. Nemožno ju iterovať v ľubovoľnom poradí.
  • Atribút id — je to byte string.

V ideálnom svete by sa dalo očakávať, že všetky takéto nuansy budú popísané vo vstavaných dokumentáciách (docstring). Ale skúsenosť poskytuje množstvo príkladov toho, že takáto dokumentácia v kóde, s ktorým sa pracuje, často chýba. Aj keď takáto dokumentácia existuje, nemožno sa spoliehať na jej absolútnu správnosť. Táto dokumentácia môže byť nejasná, nepresná a môže otvárať množstvo možností na jej nesprávne pochopenie. V veľkých tímoch alebo v rozsiahlych projektoch sa tento problém môže stať mimoriadne akutným.

Hoci Python sa výborne osvedčuje v raných alebo stredných fázach projektov, v určitom momente môžu úspešné projekty a spoločnosti používajúce Python čeliť životne dôležitej otázke: 'Potrebujeme všetko prepísať do staticky typizovaného jazyka?'.

Systémy kontroly typov ako mypy riešia uvedený problém tým, že poskytujú vývojárom formálny jazyk na popis typov a kontrolujú, či popisy typov zodpovedajú implementáciám programov (a, čo nie je nevyhnutné, kontrolujú ich existenciu). Všeobecne sa dá povedať, že tieto systémy nám poskytujú akúsi starostlivo overenú dokumentáciu.

Používanie týchto systémov má aj ďalšie výhody, ktoré sú už úplne netriviálne:

  • Systém kontroly typov môže odhaliť niektoré drobné (ale aj nie veľmi drobné) chyby. Typickým príkladom je, keď zabudnú spracovať hodnotu None alebo nejakú inú špeciálnu podmienku.
  • Refaktoring kódu sa výrazne zjednodušuje, pretože systém kontroly typov často veľmi presne uvádza, ktorý kód je potrebné zmeniť. Pri tom sa nemusíme spoliehať na 100% pokrytie kódu testami, čo je v každom prípade zvyčajne neuskutočniteľné. Nemusíme preskúmavať hlboké výpisy trasovania zásobníka, aby sme zistili príčinu poruchy.
  • Aj v rozsiahlych projektoch môže mypy často vykonať kompletné overenie typov za zlomky sekundy. Vykonanie testov zvyčajne trvá desiatky sekúnd alebo dokonca minúty. Systém overovania typov poskytuje programátorovi okamžitú spätnú väzbu a umožňuje mu rýchlejšie robiť svoju prácu. Už nemusí písať krehké a ťažko udržiavateľné modulárne testy, ktoré nahrádzajú reálne entity faux a záplatami len pre rýchlejšie získanie výsledkov testovania kódu.

IDE a editory ako PyCharm alebo Visual Studio Code využívajú možnosti anotácií typov na to, aby vývojárom poskytli funkcionalitu automatického doplňovania kódu, zvýrazňovania chýb a podporu často používaných jazykových konštrukcií. A toto sú len niektoré z výhod, ktoré prináša typizácia. Pre niektorých programátorov sú to hlavné argumenty v prospech typizácie. Je to niečo, čo prináša užitočné výsledky okamžite po zavedení do práce. Tento variant použitia typov nevyžaduje aplikáciu samostatného systému overovania typov ako mypy, hoci je potrebné poznamenať, že mypy pomáha udržiavať súlad medzi anotáciami typov a kódom.

Pozadie mypy

História mypy sa začala vo Veľkej Británii, v Cambridge, pred niekoľkými rokmi, ako som sa pripojil k Dropboxu. V rámci môjho doktorandského výskumu som sa zaoberal problematikou unifikácie staticky typovaných a dynamických jazykov. Inšpirovala ma článok o postupnej typizácii od Jeremyho Sieka a Valida Taha, ako aj projekt Typed Racket. Snažil som sa nájsť spôsoby, ako používať ten istý programovací jazyk pre rôzne projekty — od malých skriptov až po kódové základy pozostávajúce z mnohých miliónov riadkov. Chcel som, aby sa v projekte akejkoľvek veľkosti nemuselo pristúpiť na príliš veľké kompromisy. Dôležitou súčasťou bola myšlienka postupného prechodu od netypizovaného prototypu k dôkladne testovanému staticky typovanému hotovému produktu. V dnešnej dobe sú tieto myšlienky vo veľkej miere považované za samozrejmosť, ale v roku 2010 to bol problém, ktorý sa stále aktívne skúmal.

Moja pôvodná práca v oblasti overovania typov nebola zameraná na Python. Namiesto toho som použil malý "domáci" jazyk AloreTu je príklad, ktorý vám umožní pochopiť, o čom sa hovorí (typové anotácie sú tu voliteľné):

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

Používanie zjednodušeného jazyka vlastného vývoja je bežný prístup v vedeckých výskumoch. Je to tak nie v poslednom rade preto, že to umožňuje rýchlo vykonávať experimenty a tiež preto, že to, čo sa netýka výskumu, môžeme bez problémov ignorovať. Realizované programovacie jazyky sú zvyčajne rozsiahlymi javmi so zložitými implementáciami, čo experimenty spomaľuje. Avšak akékoľvek výsledky založené na zjednodušenom jazyku vyzerajú trochu podozrivo, pretože pri získavaní týchto výsledkov môže výskumník obetovať úvahy dôležité pre praktické použitie jazykov.

Moje nástroje na kontrolu typov pre Alore vyzerali veľmi sľubne, ale chcel som ich otestovať experimentovaním s reálnym kódom, ktorého, povedzme, na Alore nebolo. Na moje šťastie, jazyk Alore bol do značnej miery založený na rovnakých myšlienkach ako Python. Bolo dostatočne jednoduché upraviť nástroj na kontrolu typov tak, aby mohol pracovať so syntaxou a sémantikou Pythonu. To umožnilo pokúsiť sa vykonať kontrolu typov na open-source Python kóde. Okrem toho som napísal transpajler na konverziu kódu napísaného v Alore na Python kód a použil ho na transláciu kódu môjho nástroja na kontrolu typov. Teraz som mal systém na kontrolu typov napísaný v Pythone, ktorý podporoval podmnožinu Pythonu, akúsi variantu tohto jazyka! (Určité architektonické rozhodnutia, ktoré dávali zmysel pre Alore, sa zle hodili pre Python, čo je stále pozorovateľné v niektorých častiach kódovej bázy mypy.)

V skutočnosti jazyk, ktorý bola moja systém typov, v tom momente nemohol byť úplne nazvaný Python: bol to variant Pythonu kvôli určitým obmedzeniam syntaxe anotácií typov Python 3.

Vyzeralo to ako zmes Java a Pythonu:

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

Jedna z mojich nápadov v tom čase spočívala v použití typových anotácií na zlepšenie výkonu prekladaním tohto druhu Pythonu do C, alebo možno do bytekódu JVM. Pokročil som do fázy písania prototypu kompilátora, ale opustil som tento plán, pretože kontrola typov sama o sebe sa zdala byť dostatočne užitočná.

Nakoniec som predstavil svoj projekt na konferencii PyCon 2013 v Santa Clare. Hovoril som o tom aj s Guidom van Rossumom, veľkorysým doživotným diktátorom Pythonu. Presvedčil ma, aby som sa vzdal vlastnej syntaxe a dodržiaval štandardnú syntaxi Pythonu 3. Python 3 podporuje anotácie funkcií, takže môj príklad mohol byť prepísaný tak, ako je uvedené nižšie, pričom vznikol normálny Python program:

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

Musel som prijať niektoré kompromisy (v prvom rade chcem zdôrazniť, že som si vymyslel vlastnú syntax práve preto). Konkrétne Python 3.3, najnovšia verzia jazyka v tom čase, nepodporoval anotácie premenných. S Guidom som e-mailom diskutoval o rôznych možnostiach syntaktického zobrazenia týchto anotácií. Rozhodli sme sa použiť komentáre s uvedením typov pre premenné. To umožnilo dosiahnuť stanovený cieľ, ale vyzeralo to trochu zbytočne zložito (Python 3.6 nám poskytol príjemnejšiu syntax):

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

Komentáre s typmi, navyše, boli užitočné na podporu Pythonu 2, ktorý nemá vstavanú podporu pre typové anotácie:

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

Ukázalo sa, že tieto (a iné) kompromisy v skutočnosti nemali veľký význam — výhody statickej typizácie viedli k tomu, že používatelia čoskoro zabudli na menej ideálnu syntaxu. Pretože v kóde Python, kde sa kontrolovali typy, neboli použité žiadne špeciálne syntaktické konštrukcie, existujúce nástroje Pythonu a procesy spracovania kódu naďalej normálne fungovali, čo výrazne uľahčilo vývojárom osvojovanie si nového nástroja.

Guido ma tiež presvedčil, aby som sa pripojil k Dropboxu po tom, čo som obhájil svoju diplomovú prácu. Tu začína to najzaujímavejšie v príbehu mypy.

Pokračování následuje…

Vážení čitatelia! Ak používate Python, prosím, podeľte sa s nami o to, akú veľkosť projektov vyvíjate v tomto jazyku.

Cesta k overeniu typov 4 miliónov riadkov kódu v Pythone. Časť 1
Cesta k overeniu typov 4 miliónov riadkov kódu v Pythone. Časť 1

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster