Út a 4 millió soros Python-kód típusellenőrzéséhez. 1. rész

Ma bemutatjuk önöknek az első részt a Dropboxban alkalmazott Python-kód típusellenőrzésével kapcsolatos anyag fordításából.

Út a 4 millió soros Python-kód típusellenőrzéséhez. 1. rész

A Dropboxban sokat írunk Pythonban. Ez az a nyelv, amelyet rendkívül széles körben használunk, mind a háttérszolgáltatásokhoz, mind az asztali kliensalkalmazásokhoz. Emellett nagy mennyiségben alkalmazunk Go-t, TypeScriptet és Rustot, de a Python a fő nyelvünk. Figyelembe véve a méreteinket, amely millió sor Python-kódot jelent, kiderült, hogy a dinamikus típusellenőrzés jelentősen megnehezítette a megértést és elkezdett komolyan befolyásolni a termelékenységet. E probléma enyhítése érdekében fokozatosan átálltunk a kódunk statikus típusellenőrzésére a mypy használatával. Valószínűleg ez a legnépszerűbb, önálló típusellenőrző rendszer a Pythonhoz. A mypy egy nyílt forráskódú projekt, amelynek fő fejlesztői a Dropboxban dolgoznak.

A Dropbox az egyik első cég volt, amely statikus típusellenőrzést vezetett be Python-kódban ilyen nagyságrendben. Ma a mypy-t ezrek használják projektekben. Ezt az eszközt számtalan alkalommal, úgynevezett "harctéri tesztnek" vetették alá. Ahhoz, hogy eljussunk oda, ahol most vagyunk, hosszú utat kellett bejárnunk. Ezen az úton számos sikertelen kezdeményezés és meghiúsult kísérlet történt. Ez az anyag a Python típusellenőrzésének történetéről szól — a nehéz kezdetektől, ami a tudományos kutatási projektem része volt, egészen a mai napig, amikor a típusellenőrzések és a típusjavaslatok már megszokottakká váltak számtalan fejlesztő számára, akik Pythonban írnak. Ezeket a mechanizmusokat most számos eszköz támogatja — mint például az IDE-k és a kódelemzők.

→ Olvasd el a második részt

Miért van szükség típusellenőrzésre?

Ha valaha használtad a dinamikusan típusos Python-t, akkor talán van némi félreértésed a statikus típusozás és a mypy körüli közelmúltbeli felhajtással kapcsolatban. Lehet, hogy pont a dinamikus típusozás miatt kedveled a Python-t, és ami most történik, az csak zavar. A statikus típusozás értékének kulcsa a megoldások skálázódása: minél nagyobb a projekted, annál inkább hajlamos vagy a statikus típusozásra, és végül annál inkább valóban szükséged van erre.

Tegyük fel, hogy egy projekt elérte a tízezernyi sor méretét, és kiderült, hogy több programozó dolgozik rajta. Használva a tapasztalatainkat, elmondhatjuk, hogy a kód megértése kulcsfontosságú a fejlesztők termelékenységének fenntartásához. Típusannotációk nélkül nehéz megállapítani, hogy például milyen argumentumokat kell átadni egy funkciónak, vagy hogy milyen típusú értékeket adhat vissza egy adott funkció. Íme néhány tipikus kérdés, amelyekre gyakran nehéz válaszolni típusannotációk alkalmazása nélkül:

  • Visszaadhatja-e ez a funkció None?
  • Minek kell lennie ennek az argumentumnak items?
  • Mi a típusa a tulajdonságnak id: egész vagy esetleg valamilyen felhasználói típusnak? str, vagy esetleg egy felhasználói típusnak?
  • Kell-e ennek az argumentumnak listának lennie? Lehet-e átadni neki egy tuple-t?

Ha megnézzük a következő, típusannotációkkal ellátott kódrészletet, és próbálunk válaszolni az ilyen kérdésekre, akkor ez egy nagyon egyszerű feladatnak tűnik:

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

  • read_metadata nem ad vissza None, mivel a visszatérési típus nem Optional[…].
  • Az argumentum items — ez egy karakterláncok sorozata. Nem iterálható tetszőleges sorrendben.
  • Attribút id — ez egy bájtokból álló karakterlánc.

Ideális világban elvárható lenne, hogy az ilyen finomságokat a beépített dokumentáció (docstring) írja le. De a tapasztalat számos példát ad arra, hogy az ilyen típusú dokumentáció a kódban, amellyel dolgozni kell, gyakran nem észlelhető. Még ha van is ilyen dokumentáció a kódban, nem lehet számítani az abszolút helyességére. Ez a dokumentáció lehet homályos, pontatlan, és rengeteg lehetőséget hagy a téves értelmezésre. Nagy csapatokban vagy nagy projektekben ez a probléma rendkívül éles lehet.

Bár a Python nagyszerűen teljesít a projekt korai vagy középső szakaszaiban, bizonyos pillanatban a sikeres projektek és a Python-t használó cégek szembesülnek egy létfontosságú kérdéssel: "Vissza kell-e írni mindent statikusan típusos nyelvre?".

Az olyan típusellenőrző rendszerek, mint a mypy, megoldják a fent említett problémát azzal, hogy formális nyelvet biztosítanak a típusok leírására, és ellenőrzik, hogy a típusleírások megfeleljenek a programok megvalósításának (és, ami nem kötelező, ellenőrzik azok létezését is). Összességében elmondható, hogy ezek a rendszerek egyfajta gondosan ellenőrzött dokumentációt biztosítanak számunkra.

Az ilyen rendszerek alkalmazásának más előnyei is vannak, és ezek már nem teljesen szokványosak:

  • A típusellenőrző rendszer képes felfedezni néhány apró (és szintén — nem is annyira apró) hibát. Egy tipikus példa, amikor elfelejtik kezelni az értéket None vagy valamilyen más különleges feltételt.
  • Lényegesen egyszerűbbé válik a kód refaktorálása, mivel a típusellenőrző rendszer gyakran nagyon pontosan tájékoztat arról, hogy milyen kódot kell megváltoztatni. Ezenkívül nem kell arra támaszkodnunk, hogy 100%-os tesztlefedettséget érjünk el, ami egyébként általában nem teljesíthető. Nem kell a verem nyomkövetési jelentéseinek mélyére ásnunk, hogy kiderítsük a hiba okát.
  • Még a nagy projektekben is a mypy gyakran képes az összes típusellenőrzést másodpercek töredéke alatt elvégezni. A tesztek végrehajtása általában másodperceket vagy akár perceket vesz igénybe. A típusellenőrző rendszer azonnali visszajelzést ad a programozónak, így gyorsabban végezheti el a feladatát. Nincs többé szüksége törékeny és nehezen karbantartható moduláris tesztek írására, amelyek a valós entitásokat csak a gyorsabb kódpróbák érdekében helyettesítik álváltozatokkal és patch-ekkel.

Az IDE-k és szerkesztők, mint a PyCharm vagy a Visual Studio Code, a típusannotációk lehetőségeit használják a fejlesztők számára, hogy automatizált kódkitöltési lehetőségeket, hibajelzéseket és a gyakran használt nyelvi konstrukciók támogatását nyújtsanak. Ez csak néhány az előnyök közül, amelyeket a típusosítás biztosít. Néhány programozó számára mindez a típusosítás melletti fő érv. Ez az, ami azonnali előnyöket nyújt a bevezetése után. A típusok használatának ez a módja nem igényel külön típusellenőrző rendszer használatát, mint például a mypy, bár meg kell jegyezni, hogy a mypy segít a típusannotációk és a kód megfelelőségének fenntartásában.

A mypy háttere

A mypy története az Egyesült Királyságban, Cambridge-ben kezdődött, néhány évvel azelőtt, hogy csatlakoztam a Dropboxhoz. Doktori kutatásom keretében statikusan típusos és dinamikus nyelvek unifikációs problémájával foglalkoztam. Jeremy Siek és Walid Taha fokozatos típusolásról szóló cikkje, valamint a Typed Racket projekt inspirált. Olyan módszereket kerestem, amelyek lehetővé teszik ugyanannak a programozási nyelvnek a használatát különböző projektekhez — a kicsi szkriptektől kezdve a többmillió soros kódalapokig. Eközben azt akartam, hogy bármilyen méretű projekt esetében ne kelljen túl nagy kompromisszumot kötnöm. A lényeg a fokozatos átmenet állítás nélküli prototípusból egy átfogóan tesztelt, statikusan típusos késztermékbe volt. Ezek az ötletek manapság nagyrészt magától értetődőnek számítanak, de 2010-ben még aktívan kutatták őket.

Az eredeti munkám a típusellenőrzés területén nem Pythonra irányult. Ehelyett egy kicsi, "házi készítésű" nyelvet használtam. AloreEz egy példa, ami segít megérteni, miről is van szó (a típusannotációk itt nem kötelezőek):

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

A saját fejlesztésű egyszerűsített nyelv használata gyakori megközelítés a tudományos kutatásokban. Ennek többek között az az oka, hogy ez lehetővé teszi a gyors kísérletezést, valamint hogy a kutatás szempontjából irreleváns dolgokat szabadon figyelmen kívül lehet hagyni. A valóban használt programozási nyelvek általában összetett megoldások, ami lelassítja a kísérleteket. Azonban a leegyszerűsített nyelven alapuló eredmények kissé gyanúsak, mivel a kutató az eredmények elérésénél esetleg lemondott olyan szempontokról, amelyek lényegesek a nyelvek gyakorlati használatához.

Az Alore-hoz készült típusellenőrző eszközöm ígéretesen nézett ki, de szerettem volna kipróbálni valós kóddal, amiből Alore-ban mondhatni nem volt sok. Szerencsémre az Alore nyelv nagymértékben a Python alapötletein nyugodott. Elég könnyű volt átalakítani a típusellenőrző eszközt, így működhetett a Python szintaxisával és szemantikájával. Ez lehetővé tette, hogy típuseseteket végezzek nyílt forráskódú Python-kódon. Ezenkívül írtam egy transzpilert, hogy az Alore-ban írt kódot Python-kóddá alakítsam, és ezt használtam a típusellenőrző eszközöm kódjának fordításához. Most már volt egy Pythonban írt típuseszközöm, ami egy Python egy alműfaját támogatta! (Néhány architekturális döntés, ami az Alore számára értelmes volt, nem illett jól a Pythonnal, ez ma is észlelhető a mypy kódalap egyes részein.)

Valójában a típusaimat támogató nyelv ebben a pillanatban nem volt teljesen Python: ez a Python egy korlátozottabb változata volt a Python 3 típusannotációs szintaxisának néhány korlátozása miatt.

Ez egy Java és Python keverékének tűnt:

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

Az egyik ötletem az volt, hogy típus annotációkat használjak a teljesítmény javítása érdekében azzal, hogy ezt a Python változatot C-be, vagy esetleg a JVM bájtkódjába fordítom. Eljutottam a fordító prototípusának megírásáig, de végül abbahagytam, mivel a típusellenőrzés önállóan is elég hasznosnak tűnt.

Végül bemutattam a projektemet a 2013-as PyCon konferencián Santa Clarában. Emellett beszéltem erről Guido van Rossummal, a Python nagylelkű élethosszig tartó diktátorával. Meggyőzött, hogy mondjak le a saját szintaxisomról, és tartsam magam a Python 3 szabványos szintaxisához. A Python 3 támogatja a függvények annotációit, ennek eredményeként a példám átírható alább látható módon, normális Python programként:

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

Több kompromisszumot is meg kellett kötnöm (elsősorban azt szeretném megemlíteni, hogy ezért találtam ki a saját szintaxisomat). Különösen a Python 3.3, a nyelv legfrissebb verziója abban az időben, nem támogatta a változók annotációit. E-mailben beszéltem Guida-val a típus annotációk szintaktikai lehetőségeiről. Úgy döntöttünk, hogy a változókhoz típusokra vonatkozó megjegyzéseket használunk. Ez lehetővé tette a kitűzött cél elérését, de kissé körülményesen nézett ki (a Python 3.6 már egy kellemesebb szintaxist adott nekünk):

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

A típuskommentárok emellett segítettek a Python 2 támogatásában, amely nem rendelkezik beépített típusannotáció támogatással:

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

Kiderült, hogy ezek a (és más) kompromisszumok valójában nem számítottak sokat — a statikus típusok előnyei miatt a felhasználók hamar elfeledték a nem teljesen tökéletes szintaxist. Mivel most a Python kódban, ahol a típusok ellenőrzése történt, nem alkalmaztak külön szintaktikai szerkezeteket, a meglévő Python eszközök és a kódfeldolgozási folyamatok továbbra is jól működtek, ami jelentősen megkönnyítette a fejlesztők számára az új eszköz bevezetését.

Guido azt is meg tudta győzni, hogy csatlakozzak a Dropboxhoz, miután megvédtem a diplomamunkámat. Itt kezdődik a történet legérdekesebb része a mypy-val.

Folytatás következik…

Használjátok a CI/CD technológiákat a projektjeitekben? Ha Python-t használ, kérjük, meséljen arról, hogy milyen méretű projekteket fejleszt ezen a nyelven.

Út a 4 millió soros Python-kód típusellenőrzéséhez. 1. rész
Út a 4 millió soros Python-kód típusellenőrzéséhez. 1. rész

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster