Polku 4 miljoonan Python-koodirivin tyyppitarkistukseen. Osa 1

Tänään tuomme tietoosi ensimmäisen osan materiaalin käännöksestä siitä, kuinka Dropbox käsittelee Python-koodin tyyppiohjausta.

Polku 4 miljoonan Python-koodirivin tyyppitarkistukseen. Osa 1

Dropbox kirjoittaa paljon Pythonissa. Se on kieli, jota käytämme erittäin laajasti sekä taustapalveluissa että työpöytäasiakassovelluksissa. Käytämme myös paljon Goa, TypeScriptiä ja Rustia, mutta pääkielimme on Python. Ottaen huomioon mittakaavamme ja puhumme miljoonista Python-koodiriveistä, kävi ilmi, että tällaisen koodin dynaaminen kirjoittaminen vaikeutti tarpeettomasti sen ymmärtämistä ja alkoi vaikuttaa vakavasti työn tuottavuuteen. Tämän ongelman lieventämiseksi olemme alkaneet vähitellen siirtää koodiamme staattiseen tyyppitarkistukseen mypyn avulla. Tämä on luultavasti suosituin Pythonin erillinen tyyppitarkistusjärjestelmä. Mypy on avoimen lähdekoodin projekti, jonka pääkehittäjät työskentelevät Dropboxissa.

Dropbox oli yksi ensimmäisistä yrityksistä, joka otti käyttöön staattisen tyyppitarkistuksen Python-koodissa tässä mittakaavassa. Mypyä käytetään nykyään tuhansissa projekteissa. Tämä työkalu lukemattomia kertoja, kuten sanotaan, "testattu taistelussa". Olemme kulkeneet pitkän tien päästäksemme siihen, missä olemme nyt. Matkan varrella oli monia epäonnistuneita yrityksiä ja epäonnistuneita kokeiluja. Tämä postaus kattaa Pythonin staattisen tyyppitarkistuksen historian sen kiviallisesta alusta osana tutkimusprojektiani nykypäivään, jolloin tyyppitarkistus ja tyyppivihjeet ovat tulleet yleisiksi lukemattomille Pythonilla kirjoittaville kehittäjille. Näitä mekanismeja tukevat nyt monet työkalut, kuten IDE:t ja koodianalysaattorit.

Lue toinen osa

Miksi tyyppitarkistus on tarpeen?

Jos olet joskus käyttänyt dynaamisesti kirjoitettua Pythonia, sinulla saattaa olla hämmennystä siitä, miksi staattisen kirjoittamisen ja mypyn ympärillä on ollut viime aikoina niin paljon meteliä. Tai ehkä pidät Pythonista juuri sen dynaamisen kirjoittamisen vuoksi, ja tapahtumat vain ärsyttävät sinua. Avain staattisen kirjoittamisen arvoon on ratkaisujen mittakaava: mitä suurempi projektisi, sitä enemmän kallistut staattiseen kirjoittamiseen, ja loppujen lopuksi sitä enemmän tarvitset sitä.

Oletetaan, että tietty projekti on saavuttanut kymmenien tuhansien rivien koon, ja kävi ilmi, että useat ohjelmoijat työskentelevät sen parissa. Kun tarkastellaan samanlaista projektia, voimme kokemuksemme perusteella sanoa, että sen koodin ymmärtäminen on avain kehittäjien tuottavuuteen. Ilman tyyppimerkintöjä voi olla vaikeaa selvittää esimerkiksi, mitä argumentteja funktiolle välitetään tai mitä tyyppejä funktio voi palauttaa. Tässä on tyypillisiä kysymyksiä, joihin on usein vaikea vastata ilman tyyppimerkintöjä:

  • Voiko tämä funktio palauttaa None?
  • Mikä tämän argumentin pitäisi olla? items?
  • Mikä on attribuutin tyyppi id: int onko se, str, tai kenties jokin mukautettu tyyppi?
  • Pitäisikö tämän väitteen olla luettelo? Onko mahdollista siirtää monikko sille?

Jos katsot seuraavaa kirjoitettua koodinpätkää ja yrität vastata samankaltaisiin kysymyksiin, tämä on yksinkertaisin tehtävä:

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

  • read_metadata ei palaa None, koska palautustyyppi ei ole Optional[…].
  • perustelu items on rivien sarja. Sitä ei voi toistaa satunnaisesti.
  • ominaisuus id on tavujen merkkijono.

Ihanteellisessa maailmassa voisi odottaa, että kaikki sellaiset hienovaraisuudet kuvattaisiin sisäänrakennetussa dokumentaatiossa (docstring). Mutta kokemus antaa paljon esimerkkejä siitä, että tällaista dokumentaatiota ei useinkaan noudateta koodissa, jonka kanssa sinun on työskenneltävä. Vaikka tällainen dokumentaatio on koodissa, ei voida luottaa sen absoluuttiseen oikeellisuuteen. Tämä dokumentaatio voi olla epämääräinen, epätarkka ja avoin väärinkäsityksille. Suurissa ryhmissä tai suurissa projekteissa tämä ongelma voi olla erittäin akuutti.

Vaikka Python on erinomainen projektien alku- tai välivaiheessa, onnistuneet projektit ja Pythonia käyttävät yritykset voivat jossain vaiheessa kohdata tärkeän kysymyksen: "Pitäisikö meidän kirjoittaa kaikki uudelleen staattisesti kirjoitetulla kielellä?".

Tyypintarkistusjärjestelmät, kuten mypy, ratkaisevat yllä olevan ongelman tarjoamalla kehittäjälle muodollisen kielen tyyppien kuvaamista varten ja tarkistamalla, että tyyppiilmoitukset vastaavat ohjelman toteutusta (ja valinnaisesti tarkistamalla niiden olemassaolon). Yleisesti voidaan sanoa, että nämä järjestelmät ovat antaneet käyttöönsä huolella tarkistetun dokumentaation.

Tällaisten järjestelmien käytöllä on muita etuja, ja ne ovat jo täysin ei-triviaaleja:

  • Tyypintarkistusjärjestelmä voi havaita pieniä (ja ei niin pieniä) virheitä. Tyypillinen esimerkki on, kun he unohtavat käsitellä arvoa None tai jokin muu erityisehto.
  • Koodin uudelleenmuodostus on yksinkertaistettu huomattavasti, koska tyypin tarkistusjärjestelmä on usein erittäin tarkka sen suhteen, mitä koodia on muutettava. Samaan aikaan meidän ei tarvitse toivoa 100-prosenttista koodikattavuutta testeillä, mikä joka tapauksessa ei yleensä ole mahdollista. Meidän ei tarvitse sukeltaa pinojäljen syvyyksiin löytääksemme ongelman syyn.
  • Jopa suurissa projekteissa mypy pystyy usein suorittamaan täyden tyypin tarkistuksen sekunnin murto-osassa. Ja testien suorittaminen kestää yleensä kymmeniä sekunteja tai jopa minuutteja. Tyyppitarkistusjärjestelmä antaa ohjelmoijalle välittömän palautteen ja antaa hänen tehdä työnsä nopeammin. Hänen ei enää tarvitse kirjoittaa hauraita ja vaikeita ylläpidettäviä yksikkötestejä, jotka korvaavat todelliset entiteetit pilkoilla ja korjaustiedostoilla vain saadakseen kooditestitulokset nopeammin.

IDE:t ja editorit, kuten PyCharm tai Visual Studio Code, käyttävät tyyppimerkintöjä tarjotakseen kehittäjille koodin täydennyksen, virheiden korostuksen ja tuen yleisesti käytetyille kielirakenteille. Ja nämä ovat vain osa kirjoittamisen eduista. Joillekin ohjelmoijille tämä kaikki on tärkein argumentti kirjoittamisen puolesta. Tästä on hyötyä heti käyttöönoton jälkeen. Tämä tyyppien käyttötapaus ei vaadi erillistä tyypintarkistusjärjestelmää, kuten mypy, vaikka on huomattava, että mypy auttaa pitämään tyyppimerkinnät yhdenmukaisina koodin kanssa.

Mypyn tausta

Mypyn historia alkoi Isossa-Britanniassa, Cambridgessa, muutama vuosi ennen kuin liityin Dropboxiin. Olen ollut osana tohtoritutkimustani mukana staattisesti tyypitettyjen ja dynaamisten kielten yhdistämisessä. Sain inspiraation Jeremy Siekin ja Walid Tahan inkrementaalista kirjoittamista käsittelevästä artikkelista sekä Typed Racket -projektista. Yritin löytää tapoja käyttää samaa ohjelmointikieltä eri projekteihin - pienistä skripteistä useista miljoonista riveistä koostuviin koodikantoihin. Samalla halusin varmistaa, että minkään mittakaavan projektissa ei tarvitse tehdä liian suuria kompromisseja. Tärkeä osa kaikkea tätä oli ajatus siirtyä asteittain tyypittämättömästä prototyyppiprojektista kattavasti testattuun staattisesti tyypitettyyn valmiiseen tuotteeseen. Nykyään näitä ajatuksia pidetään suurelta osin itsestäänselvyytenä, mutta vuonna 2010 se oli ongelma, jota vielä aktiivisesti selvitettiin.

Alkuperäinen työni tyyppitarkistuksessa ei ollut suunnattu Pythonille. Sen sijaan käytin pientä "kotitekoista" kieltä Alore. Tässä on esimerkki, jonka avulla ymmärrät, mistä puhumme (tyyppimerkinnät ovat valinnaisia ​​tässä):

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

Yksinkertaistetun äidinkielen käyttö on yleinen lähestymistapa tieteellisessä tutkimuksessa. Näin on, ei vähiten siksi, että sen avulla voit suorittaa nopeasti kokeita, ja myös siksi, että se, mikä ei liity tutkimukseen, voidaan helposti jättää huomiotta. Reaalimaailman ohjelmointikielet ovat yleensä laajamittaisia ​​ilmiöitä monimutkaisine toteutuksineen, mikä hidastaa kokeilua. Yksinkertaistettuun kieleen perustuvat tulokset näyttävät kuitenkin hieman epäilyttävältä, sillä näitä tuloksia saaessaan tutkija on saattanut uhrata kielten käytännön käytön kannalta tärkeitä näkökohtia.

Tyyppitarkistusni Alorelle näytti erittäin lupaavalta, mutta halusin testata sitä kokeilemalla oikeaa koodia, jota ei ehkä kirjoitettu Aloressa. Onneksi Alore-kieli perustui pitkälti samoihin ideoihin kuin Python. Tyypintarkistus oli tarpeeksi helppoa muuttaa niin, että se toimi Pythonin syntaksin ja semantiikan kanssa. Tämä antoi meille mahdollisuuden kokeilla tyyppitarkistusta avoimen lähdekoodin Python-koodissa. Lisäksi kirjoitin transpilerin muuntamaan Aloressa kirjoitetun koodin Python-koodiksi ja käytin sitä kääntämään typechecker-koodini. Nyt minulla oli Pythonilla kirjoitettu tyypintarkistusjärjestelmä, joka tuki Pythonin osajoukkoa, jonkinlaista tuota kieltä! (Tietyt Alorelle järkevät arkkitehtoniset päätökset sopisivat huonosti Pythonille, ja tämä on edelleen havaittavissa osissa mypy-koodikantaa.)

Itse asiassa tyyppijärjestelmäni tukemaa kieltä ei tässä vaiheessa voitu kutsua aivan Pythoniksi: se oli Pythonin muunnos Python 3 -tyyppisen merkintäsyntaksin rajoitusten vuoksi.

Se näytti Javan ja Pythonin sekoitukselta:

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

Yksi ajatukseni tuolloin oli käyttää tyyppimerkintöjä suorituskyvyn parantamiseksi kääntämällä tällainen Python C:ksi tai ehkä JVM-tavukoodiksi. Pääsin kääntäjän prototyypin kirjoittamisen vaiheeseen, mutta hylkäsin tämän idean, koska itse tyyppitarkistus vaikutti varsin hyödylliseltä.

Päädyin esittelemään projektini PyCon 2013:ssa Santa Clarassa. Puhuin tästä myös Guido van Rossumin, hyväntahtoisen Python-diktaattorin kanssa. Hän sai minut luopumaan omasta syntaksista ja pysymään Python 3:n vakiosyntaksissa. Python 3 tukee funktiomerkintöjä, joten esimerkkini voitaisiin kirjoittaa uudelleen alla olevan kuvan mukaisesti, jolloin tuloksena on normaali Python-ohjelma:

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

Jouduin tekemään kompromisseja (ensinnäkin haluan huomata, että keksin oman syntaksin juuri tästä syystä). Erityisesti Python 3.3, tuolloin uusin versio kielestä, ei tukenut muuttujamerkintöjä. Keskustelin Guidon kanssa sähköpostitse erilaisista mahdollisuuksista tällaisten huomautusten syntaktiseen suunnitteluun. Päätimme käyttää muuttujille tyyppikommentteja. Tämä palveli aiottua tarkoitusta, mutta oli hieman hankala (Python 3.6 antoi meille mukavamman syntaksin):

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

Kirjoituskommentit olivat myös hyödyllisiä tukemaan Python 2:ta, jossa ei ole sisäänrakennettua tukea tyyppimerkintöille:

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

Kävi ilmi, että näillä (ja muilla) kompromissilla ei ollut väliä - staattisen kirjoittamisen edut tarkoittivat sitä, että käyttäjät unohtivat pian vähemmän kuin täydellisen syntaksin. Koska Python-koodissa ei käytetty erityistä syntaksia, joka oli tyyppitarkistettu, olemassa olevat Python-työkalut ja koodinkäsittelyprosessit jatkoivat toimintaansa normaalisti, mikä helpotti uuden työkalun oppimista kehittäjille.

Guido myös sai minut liittymään Dropboxiin valmistuttuani opinnäytetyöni. Tästä alkaa mypy-tarinan mielenkiintoisin osa.

Jatkuu ...

Hyvä lukijat! Jos käytät Pythonia, kerro meille tällä kielellä kehittämiesi projektien laajuudesta.

Polku 4 miljoonan Python-koodirivin tyyppitarkistukseen. Osa 1
Polku 4 miljoonan Python-koodirivin tyyppitarkistukseen. Osa 1

Lähde: will.com

Lisää kommentti