4 millió Python-kódsor típusellenőrzésének elérési útja. 1. rész

Ma figyelmükbe ajánljuk az anyag fordításának első részét, amely arról szól, hogy a Dropbox hogyan kezeli a Python-kód típusvezérlését.

4 millió Python-kódsor típusellenőrzésének elérési útja. 1. rész

A Dropbox sokat ír Pythonban. Ez egy olyan nyelv, amelyet rendkívül széles körben használunk, mind a háttérszolgáltatásokhoz, mind az asztali ügyfélalkalmazásokhoz. A Go, a TypeScript és a Rust is sokat használunk, de a Python a fő nyelvünk. Ha figyelembe vesszük a méretünket, és több millió Python-kódsorról beszélünk, kiderült, hogy az ilyen kód dinamikus beírása szükségtelenül bonyolította a megértést, és komolyan befolyásolta a munka termelékenységét. A probléma enyhítése érdekében elkezdtük fokozatosan átállítani a kódunkat a statikus típusellenőrzésre a mypy használatával. Valószínűleg ez a legnépszerűbb önálló típusellenőrző rendszer a Python számára. A Mypy egy nyílt forráskódú projekt, fő fejlesztői a Dropboxban dolgoznak.

A Dropbox volt az egyik első olyan vállalat, amely ilyen léptékben telepítette a statikus típusellenőrzést Python kódban. A Mypy-t manapság több ezer projektben használják. Ezt az eszközt számtalanszor, ahogy mondják, "csatában tesztelték". Hosszú utat tettünk meg, hogy eljussunk odáig, ahol most tartunk. Útközben sok sikertelen vállalkozás és kudarcba fulladt kísérlet volt. Ez a bejegyzés a Python statikus típusellenőrzésének történetét mutatja be, a kutatási projektem részeként kialakult kőkemény kezdetektől napjainkig, amikor a típusellenőrzés és a típusujgatás mindennapossá vált számtalan Python nyelven író fejlesztő számára. Ezeket a mechanizmusokat ma már számos eszköz támogatja, például IDE-k és kódelemzők.

Olvassa el a második részt

Miért szükséges a típusellenőrzés?

Ha valaha is használta a dinamikusan gépelt Pythont, akkor lehet, hogy némi zavarban van, miért van mostanában ekkora felhajtás a statikus gépelés és a mypy körül. Vagy talán éppen a dinamikus gépelése miatt szereted a Pythont, és ami történik, egyszerűen felzaklat. A statikus gépelés értékének kulcsa a megoldások léptéke: minél nagyobb a projektje, annál inkább hajlik a statikus gépelésre, és végül annál nagyobb szüksége van rá.

Tegyük fel, hogy egy bizonyos projekt elérte a több tízezer sor méretét, és kiderült, hogy több programozó is dolgozik rajta. Egy hasonló projektet tekintve tapasztalataink alapján elmondhatjuk, hogy a kód megértése lesz a kulcs a fejlesztők termelékenységének megőrzéséhez. Típusannotációk nélkül nehéz lehet kitalálni például, hogy milyen argumentumokat kell átadni egy függvénynek, vagy milyen típusokat adhat vissza egy függvény. Íme tipikus kérdések, amelyekre gyakran nehéz megválaszolni típusjegyzetek használata nélkül:

  • Visszatérhet-e ez a függvény None?
  • Mi legyen ez az érv? items?
  • Mi az attribútum típusa id: int ez, str, vagy esetleg valami egyedi típus?
  • Ennek az érvnek egy listának kell lennie? Át lehet adni neki egy sort?

Ha megnézi a következő, típusjegyzett kódrészletet, és megpróbál hasonló kérdésekre válaszolni, kiderül, hogy ez a legegyszerűbb feladat:

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

  • read_metadata nem tér vissza None, mivel a visszatérési típus nem Optional[…].
  • Érv items egy sorok sorozata. Nem iterálható véletlenszerűen.
  • Tulajdonság id egy bájtokból álló karakterlánc.

Egy ideális világban azt várnánk, hogy minden ilyen finomság le legyen írva a beépített dokumentációban (docstring). De a tapasztalat sok példát ad arra, hogy az ilyen dokumentáció gyakran nem figyelhető meg a kódban, amellyel dolgoznia kell. Még ha ilyen dokumentáció is szerepel a kódban, nem számíthatunk annak abszolút helyességére. Ez a dokumentáció homályos, pontatlan és félreértésekre vezethető vissza. Nagy csapatoknál vagy nagy projekteknél ez a probléma rendkívül akuttá válhat.

Míg a Python a projektek korai vagy középső szakaszában jeleskedik, egy ponton a sikeres projektek és a Pythont használó vállalatok szembesülhetnek azzal a létfontosságú kérdéssel: „Át kell írni mindent egy statikusan gépelt nyelven?”.

A típusellenőrző rendszerek, mint például a mypy, úgy oldják meg a fenti problémát, hogy a fejlesztő számára formális nyelvet biztosítanak a típusok leírásához, és ellenőrzik, hogy a típusdeklarációk megfelelnek-e a program megvalósításának (és opcionálisan ellenőrzik a létezésüket). Általánosságban elmondható, hogy ezek a rendszerek mintegy gondosan ellenőrzött dokumentációt bocsátanak rendelkezésünkre.

Az ilyen rendszerek használatának más előnyei is vannak, és ezek már teljesen nem triviálisak:

  • A típusellenőrző rendszer képes észlelni néhány apró (és nem is olyan kicsi) hibát. Tipikus példa, amikor elfelejtenek feldolgozni egy értéket None vagy más speciális feltétel.
  • A kód átalakítása nagymértékben leegyszerűsödik, mivel a típusellenőrző rendszer gyakran nagyon pontosan meghatározza, hogy milyen kódot kell megváltoztatni. Ugyanakkor nem kell 100%-os kódlefedettséget remélnünk a tesztekkel, ami egyébként általában nem kivitelezhető. Nem kell a veremnyomok mélyére ásnunk, hogy megtudjuk a probléma okát.
  • A mypy még nagy projekteknél is gyakran a másodperc töredéke alatt képes teljes típusellenőrzést végezni. A tesztek végrehajtása pedig általában több tíz másodpercet vagy akár perceket is igénybe vesz. A típusellenőrző rendszer azonnali visszajelzést ad a programozónak, és gyorsabban végezheti munkáját. Többé nem kell törékeny és nehezen karbantartható egységteszteket írnia, amelyek a valódi entitásokat gúnyokkal és foltokkal helyettesítik, csak azért, hogy gyorsabban megkapják a kódteszt eredményeit.

Az IDE-k és szerkesztők, mint például a PyCharm vagy a Visual Studio Code, a típusjegyzetek erejét használják arra, hogy a fejlesztők számára kódkiegészítést, hibakiemelést és az általánosan használt nyelvi konstrukciók támogatását biztosítsák. És ez csak néhány a gépelés előnyei közül. Egyes programozók számára mindez a fő érv a gépelés mellett. Ez olyan dolog, amely közvetlenül a megvalósítás után előnyös. Ez a típushasználati eset nem igényel külön típus-ellenőrző rendszert, mint például a mypy, bár meg kell jegyezni, hogy a mypy segít a típusjegyzetek kóddal való konzisztenciájá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 részeként statikusan tipizált és dinamikus nyelvek egységesítésében vettem részt. Ihletet kaptam Jeremy Siek és Walid Taha növekményes gépelésről szóló cikke, valamint a Typed Racket projekt. Megpróbáltam megtalálni a módját, hogy ugyanazt a programozási nyelvet használhassuk különböző projektekhez – a kis szkriptektől a sok millió sorból álló kódbázisokig. Ugyanakkor biztosítani akartam, hogy egy bármilyen léptékű projektben ne kelljen túl nagy kompromisszumokat kötni. Mindennek fontos része volt az az ötlet, hogy a típus nélküli prototípus projektről fokozatosan áttérjünk egy átfogóan tesztelt statikusan tipizált késztermékre. Manapság ezek az ötletek nagyrészt természetesnek számítanak, de 2010-ben ez egy olyan probléma volt, amelyet még aktívan vizsgáltak.

Eredeti munkám a típusellenőrzéssel nem a Pythonra irányult. Ehelyett egy kis "házi" nyelvet használtam Alore. Íme egy példa, amely segít megérteni, miről beszélünk (itt a típusjegyzetek 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

Az egyszerűsített anyanyelv használata a tudományos kutatásban elterjedt megközelítés. Ez nem utolsósorban azért van így, mert lehetővé teszi a kísérletek gyors elvégzését, és azért is, mert aminek semmi köze a vizsgálathoz, könnyen figyelmen kívül hagyható. A valós programozási nyelvek általában nagyszabású jelenségek, bonyolult megvalósításokkal, és ez lelassítja a kísérletezést. Az egyszerűsített nyelven alapuló eredmények azonban kissé gyanúsnak tűnnek, mivel ezen eredmények megszerzése során a kutató feláldozhatott a nyelvek gyakorlati használata szempontjából fontos szempontokat.

Az Alore típusellenőrzőm nagyon ígéretesnek tűnt, de ki akartam próbálni valódi kóddal kísérletezve, ami, mondhatni, nem Alore-ban íródott. Szerencsémre az Alore nyelv nagyrészt ugyanazokon a gondolatokon alapult, mint a Python. Elég könnyű volt megváltoztatni a típusellenőrzőt, hogy működjön a Python szintaxisával és szemantikájával. Ez lehetővé tette számunkra, hogy kipróbáljuk a típusellenőrzést nyílt forráskódú Python kódban. Ezenkívül írtam egy transzpilert, amely az Alore-ban írt kódot Python kódra konvertálja, és ezzel lefordítottam a típusellenőrző kódomat. Most volt egy Pythonban írt típusellenőrző rendszerem, amely támogatta a Python egy részhalmazát, valamiféle nyelvet! (Bizonyos építészeti döntések, amelyeknek értelme volt az Alore számára, nem voltak megfelelőek a Python számára, és ez még mindig észrevehető a mypy kódbázis egyes részein.)

Valójában a típusrendszerem által támogatott nyelvet jelenleg nem lehetett Pythonnak nevezni: a Python egyik változata volt a Python 3 típusú annotáció szintaxisának bizonyos korlátai miatt.

Úgy nézett ki, mint a Java és a Python keveréke:

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

Az egyik akkori ötletem az volt, hogy típusjegyzetekkel javítsam a teljesítményt az ilyen típusú Python C-be, vagy esetleg JVM bájtkódba való fordításával. Eljutottam egy fordító prototípus megírásáig, de elvetettem ezt az ötletet, mivel maga a típusellenőrzés nagyon hasznosnak tűnt.

A projektemet a 2013-as PyCon-on mutattam be Santa Clarában. Erről beszéltem Guido van Rossummal is, a jóindulatú Python diktátorral. Meggyőzött, hogy hagyjam el a saját szintaxisomat, és maradjak a szabványos Python 3 szintaxisnál. A Python 3 támogatja a függvényannotációkat, így a példámat az alábbiak szerint át lehet írni, ami egy normál Python programot eredményez:

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

Néhány kompromisszumot kellett kötnöm (először is szeretném megjegyezni, hogy pont ezért találtam ki a saját szintaxisomat). Különösen a Python 3.3, a nyelv akkori legújabb verziója nem támogatta a változó megjegyzéseket. Megbeszéltem Guidóval e-mailben az ilyen megjegyzések szintaktikai tervezésének különféle lehetőségeit. Úgy döntöttünk, hogy a változókhoz típus megjegyzéseket használunk. Ez megfelelt a célnak, de kissé nehézkes volt (a Python 3.6 szebb szintaxist adott nekünk):

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

A szöveges megjegyzések szintén hasznosak voltak a Python 2 támogatásához, amely nem rendelkezik beépített támogatással a típusjegyzetekhez:

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

Kiderült, hogy ezek (és más) kompromisszumok nem igazán számítanak – a statikus gépelés előnyei miatt a felhasználók hamar megfeledkeztek a kevésbé tökéletes szintaxisról. Mivel a típus-ellenőrzött Python-kódban nem használtak speciális szintaktikai konstrukciókat, a meglévő Python-eszközök és kódfeldolgozó folyamatok továbbra is normálisan működtek, így a fejlesztők sokkal könnyebben tanulhatják meg az új eszközt.

Guido is meggyőzött, hogy csatlakozzam a Dropboxhoz, miután befejeztem a diplomamunkámat. Itt kezdődik a mypy történet legérdekesebb része.

Folytatás ...

Kedves olvasók! Ha Pythont használ, kérjük, mondja el nekünk az ezen a nyelven fejlesztett projektek méretét.

4 millió Python-kódsor típusellenőrzésének elérési útja. 1. rész
4 millió Python-kódsor típusellenőrzésének elérési útja. 1. rész

Forrás: will.com

Hozzászólás