Ang landas sa pag-typecheck ng 4 na milyong linya ng Python code. Bahagi 1

Ngayon dinadala namin sa iyong pansin ang unang bahagi ng pagsasalin ng materyal sa kung paano nakikipag-ugnayan ang Dropbox sa kontrol ng uri ng Python code.

Ang landas sa pag-typecheck ng 4 na milyong linya ng Python code. Bahagi 1

Maraming nagsusulat ang Dropbox sa Python. Ito ay isang wika na ginagamit namin nang napakalawak, kapwa para sa mga back-end na serbisyo at mga desktop client application. Madalas din kaming gumagamit ng Go, TypeScript at Rust, ngunit ang Python ang aming pangunahing wika. Isinasaalang-alang ang aming sukat, at pinag-uusapan natin ang tungkol sa milyun-milyong linya ng Python code, lumabas na ang pabago-bagong pag-type ng naturang code ay hindi kinakailangang kumplikado sa pag-unawa nito at nagsimulang seryosong makaapekto sa produktibidad ng paggawa. Upang mabawasan ang problemang ito, sinimulan naming unti-unting ilipat ang aming code sa static type checking gamit ang mypy. Ito marahil ang pinakasikat na standalone type checking system para sa Python. Ang Mypy ay isang open source na proyekto, ang mga pangunahing developer nito ay gumagana sa Dropbox.

Ang Dropbox ay isa sa mga unang kumpanya na nagpatupad ng static type checking sa Python code sa sukat na ito. Ang Mypy ay ginagamit sa libu-libong mga proyekto sa mga araw na ito. Ang tool na ito ay hindi mabilang na beses, gaya ng sinasabi nila, "nasubok sa labanan." Malayo na ang narating natin para makarating sa kinalalagyan natin ngayon. Sa daan, maraming hindi matagumpay na gawain at nabigong mga eksperimento. Sinasaklaw ng post na ito ang kasaysayan ng static type checking sa Python, mula sa mabatong simula nito bilang bahagi ng aking proyekto sa pagsasaliksik, hanggang sa kasalukuyan, kung kailan naging pangkaraniwan na ang type checking at type hinting para sa hindi mabilang na mga developer na nagsusulat sa Python. Ang mga mekanismong ito ay sinusuportahan na ngayon ng maraming tool tulad ng mga IDE at code analyzer.

β†’ Basahin ang ikalawang bahagi

Bakit kailangan ang type checking?

Kung nakagamit ka na ng dynamic na pag-type ng Python, maaaring magkaroon ka ng ilang pagkalito kung bakit nagkaroon ng kaguluhan tungkol sa static na pag-type at mypy kamakailan. O baka gusto mo ang Python dahil mismo sa pabago-bagong pag-type nito, at nakakainis ka lang sa mga nangyayari. Ang susi sa halaga ng static na pag-type ay ang sukat ng mga solusyon: kung mas malaki ang iyong proyekto, mas mahilig ka sa static na pag-type, at sa huli, mas kailangan mo ito.

Ipagpalagay na ang isang partikular na proyekto ay umabot sa laki ng sampu-sampung libong mga linya, at ito ay lumabas na maraming mga programmer ang nagtatrabaho dito. Sa pagtingin sa isang katulad na proyekto, batay sa aming karanasan, masasabi naming ang pag-unawa sa code nito ang magiging susi sa pagpapanatiling produktibo ng mga developer. Kung walang mga uri ng anotasyon, maaaring mahirap malaman, halimbawa, kung anong mga argumento ang ipapasa sa isang function, o kung anong mga uri ang maaaring ibalik ng isang function. Narito ang mga karaniwang tanong na kadalasang mahirap sagutin nang hindi gumagamit ng mga uri ng anotasyon:

  • Maaari bang bumalik ang function na ito None?
  • Ano dapat ang argumentong ito? items?
  • Ano ang uri ng katangian id: int ito ba, str, o baka ilang custom na uri?
  • Dapat bang isang listahan ang argumentong ito? Posible bang magpasa ng tuple dito?

Kung titingnan mo ang sumusunod na type-annotated na code snippet at susubukan mong sagutin ang mga katulad na tanong, lumalabas na ito ang pinakasimpleng gawain:

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

  • read_metadata hindi bumabalik None, dahil ang uri ng pagbabalik ay hindi Optional[…].
  • argumento items ay isang pagkakasunod-sunod ng mga linya. Hindi ito maaaring umulit nang random.
  • Katangian id ay isang string ng mga byte.

Sa isang perpektong mundo, aasahan ng isang tao na ang lahat ng naturang mga subtleties ay ilalarawan sa built-in na dokumentasyon (docstring). Ngunit ang karanasan ay nagbibigay ng maraming mga halimbawa ng katotohanan na ang naturang dokumentasyon ay madalas na hindi sinusunod sa code kung saan kailangan mong magtrabaho. Kahit na ang naturang dokumentasyon ay naroroon sa code, hindi maaasahan ng isa ang ganap na kawastuhan nito. Ang dokumentasyong ito ay maaaring malabo, hindi tumpak, at bukas sa mga hindi pagkakaunawaan. Sa malalaking koponan o malalaking proyekto, ang problemang ito ay maaaring maging lubhang talamak.

Habang ang Python ay nangunguna sa mga maaga o intermediate na yugto ng mga proyekto, sa ilang mga punto ang matagumpay na mga proyekto at kumpanya na gumagamit ng Python ay maaaring harapin ang mahalagang tanong: "Dapat ba nating isulat muli ang lahat sa isang statically typed na wika?".

Ang mga sistema ng pagsuri ng uri tulad ng mypy ay nilulutas ang problema sa itaas sa pamamagitan ng pagbibigay sa developer ng isang pormal na wika para sa paglalarawan ng mga uri, at sa pamamagitan ng pagsuri na ang mga uri ng deklarasyon ay tumutugma sa pagpapatupad ng programa (at, opsyonal, sa pamamagitan ng pagsuri sa kanilang pag-iral). Sa pangkalahatan, masasabi nating ang mga system na ito ay naglalagay sa amin ng isang bagay tulad ng maingat na sinuri na dokumentasyon.

Ang paggamit ng mga naturang sistema ay may iba pang mga pakinabang, at sila ay ganap na hindi mahalaga:

  • Ang sistema ng pagsusuri ng uri ay maaaring makakita ng ilang maliliit (at hindi gaanong maliit) na mga error. Ang isang karaniwang halimbawa ay kapag nakalimutan nilang iproseso ang isang halaga None o ilang iba pang espesyal na kondisyon.
  • Ang code refactoring ay lubos na pinasimple dahil ang type checking system ay kadalasang napakatumpak tungkol sa kung anong code ang kailangang baguhin. Kasabay nito, hindi namin kailangang umasa para sa 100% na saklaw ng code na may mga pagsubok, na, sa anumang kaso, ay karaniwang hindi magagawa. Hindi na natin kailangang busisiin ang lalim ng stack trace para malaman ang sanhi ng problema.
  • Kahit na sa malalaking proyekto, madalas na magagawa ng mypy ang buong uri ng pagsusuri sa isang bahagi ng isang segundo. At ang pagsasagawa ng mga pagsusulit ay karaniwang tumatagal ng sampu-sampung segundo o kahit na minuto. Ang sistema ng pagsuri ng uri ay nagbibigay ng instant feedback sa programmer at nagbibigay-daan sa kanya na gawin ang kanyang trabaho nang mas mabilis. Hindi na niya kailangang magsulat ng mga marupok at mahirap i-maintain ang mga unit test na pumapalit sa mga totoong entity ng mga mock at patch para lang makakuha ng mga resulta ng code test nang mas mabilis.

Ginagamit ng mga IDE at editor gaya ng PyCharm o Visual Studio Code ang kapangyarihan ng mga uri ng anotasyon upang mabigyan ang mga developer ng pagkumpleto ng code, pag-highlight ng error, at suporta para sa mga karaniwang ginagamit na pagbuo ng wika. At ito ay ilan lamang sa mga pakinabang ng pag-type. Para sa ilang mga programmer, ang lahat ng ito ay ang pangunahing argumento na pabor sa pag-type. Ito ay isang bagay na kaagad na nakikinabang pagkatapos ng pagpapatupad. Ang use case na ito para sa mga uri ay hindi nangangailangan ng isang hiwalay na uri ng checking system tulad ng mypy, bagama't dapat tandaan na ang mypy ay tumutulong na panatilihing pare-pareho ang mga anotasyon ng uri sa code.

Background ng mypy

Nagsimula ang kasaysayan ng mypy sa UK, sa Cambridge, ilang taon bago ako sumali sa Dropbox. Ako ay kasangkot, bilang bahagi ng aking doktoral na pananaliksik, sa pag-iisa ng statically typed at dynamic na mga wika. Na-inspire ako sa isang artikulo sa incremental na pag-type nina Jeremy Siek at Walid Taha, at ng proyektong Typed Racket. Sinubukan kong maghanap ng mga paraan upang magamit ang parehong programming language para sa iba't ibang mga proyekto - mula sa maliliit na script hanggang sa mga base ng code na binubuo ng maraming milyon-milyong mga linya. Kasabay nito, nais kong tiyakin na sa isang proyekto ng anumang sukat, hindi na kailangang gumawa ng napakalaking kompromiso. Ang isang mahalagang bahagi ng lahat ng ito ay ang ideya ng unti-unting paglipat mula sa isang hindi na-type na prototype na proyekto sa isang komprehensibong nasubok na statically typed tapos na produkto. Sa mga araw na ito, ang mga ideyang ito ay higit na tinatanggap, ngunit noong 2010 ito ay isang problema na aktibong ginalugad pa rin.

Ang aking orihinal na gawain sa pagsuri ng uri ay hindi naglalayong sa Python. Sa halip, gumamit ako ng maliit na "homemade" na wika Alore. Narito ang isang halimbawa na magbibigay-daan sa iyong maunawaan kung ano ang pinag-uusapan natin (ang mga uri ng anotasyon ay opsyonal dito):

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

Ang paggamit ng pinasimple na katutubong wika ay isang karaniwang diskarte na ginagamit sa siyentipikong pananaliksik. Ito ay gayon, hindi bababa sa dahil pinapayagan ka nitong mabilis na magsagawa ng mga eksperimento, at dahil din sa katotohanan na kung ano ang walang kinalaman sa pag-aaral ay madaling mabalewala. Ang mga real-world programming language ay may posibilidad na maging malakihang phenomena na may mga kumplikadong pagpapatupad, at ito ay nagpapabagal sa pag-eksperimento. Gayunpaman, ang anumang mga resulta batay sa isang pinasimpleng wika ay mukhang medyo kahina-hinala, dahil sa pagkuha ng mga resultang ito ang mananaliksik ay maaaring nagsakripisyo ng mga pagsasaalang-alang na mahalaga para sa praktikal na paggamit ng mga wika.

Ang aking type checker para sa Alore ay mukhang napaka-promising, ngunit gusto kong subukan ito sa pamamagitan ng pag-eksperimento sa totoong code, na, maaari mong sabihin, ay hindi nakasulat sa Alore. Sa kabutihang-palad para sa akin, ang wikang Alore ay higit na nakabatay sa parehong mga ideya tulad ng Python. Sapat na madaling gawin muli ang typechecker upang gumana ito sa syntax at semantics ng Python. Nagbigay-daan ito sa amin na subukan ang pagsuri ng uri sa open source Python code. Bilang karagdagan, nagsulat ako ng isang transpiler upang i-convert ang code na nakasulat sa Alore sa Python code at ginamit ito upang isalin ang aking typechecker code. Ngayon ay mayroon akong type checking system na nakasulat sa Python na sumusuporta sa isang subset ng Python, isang uri ng wikang iyon! (Ang ilang mga desisyon sa arkitektura na may katuturan para sa Alore ay hindi angkop para sa Python, at ito ay kapansin-pansin pa rin sa mga bahagi ng mypy codebase.)

Sa katunayan, ang wikang sinusuportahan ng aking type system ay hindi masyadong matatawag na Python sa puntong ito: isa itong variant ng Python dahil sa ilang limitasyon ng Python 3 type annotation syntax.

Mukhang pinaghalong Java at Python:

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

Ang isa sa aking mga ideya noong panahong iyon ay ang paggamit ng mga uri ng anotasyon upang mapabuti ang pagganap sa pamamagitan ng pag-compile ng ganitong uri ng Python sa C, o marahil JVM bytecode. Nakarating ako sa yugto ng pagsulat ng isang prototype ng compiler, ngunit tinalikuran ko ang ideyang ito, dahil ang pagsuri ng uri mismo ay mukhang kapaki-pakinabang.

Natapos ko ang pagtatanghal ng aking proyekto sa PyCon 2013 sa Santa Clara. Nakipag-usap din ako tungkol dito kay Guido van Rossum, ang mabait na diktador ng Python habang-buhay. Nakumbinsi niya akong i-drop ang sarili kong syntax at manatili sa karaniwang Python 3 syntax. Sinusuportahan ng Python 3 ang mga function annotation, kaya ang aking halimbawa ay maaaring muling isulat tulad ng ipinapakita sa ibaba, na nagreresulta sa isang normal na Python program:

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

Kailangan kong gumawa ng ilang mga kompromiso (una sa lahat, gusto kong tandaan na nag-imbento ako ng sarili kong syntax para sa mismong kadahilanang ito). Sa partikular, ang Python 3.3, ang pinakabagong bersyon ng wika noong panahong iyon, ay hindi sumusuporta sa mga variable na anotasyon. Nakipag-usap ako kay Guido sa pamamagitan ng e-mail ng iba't ibang mga posibilidad para sa syntactic na disenyo ng naturang mga anotasyon. Nagpasya kaming gumamit ng mga komento sa uri para sa mga variable. Nagsilbi ito sa nilalayon na layunin, ngunit medyo mahirap (Python 3.6 ay nagbigay sa amin ng mas magandang syntax):

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

Ang mga komento sa pag-type ay magagamit din upang suportahan ang Python 2, na walang built-in na suporta para sa mga uri ng anotasyon:

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

Ito ay lumabas na ang mga ito (at iba pang) trade-off ay hindi talaga mahalaga - ang mga benepisyo ng static na pag-type ay nangangahulugan na ang mga user ay nakalimutan sa lalong madaling panahon ang tungkol sa hindi gaanong perpektong syntax. Dahil walang mga espesyal na syntactic construct na ginamit sa type-checked na Python code, patuloy na gumana nang normal ang mga kasalukuyang tool sa Python at proseso ng pagpoproseso ng code, na ginagawang mas madali para sa mga developer na matutunan ang bagong tool.

Nakumbinsi din ako ni Guido na sumali sa Dropbox pagkatapos kong makumpleto ang aking graduate thesis. Dito nagsisimula ang pinakakawili-wiling bahagi ng mypy story.

Upang patuloy ...

Minamahal na mambabasa! Kung gumagamit ka ng Python, mangyaring sabihin sa amin ang tungkol sa laki ng mga proyektong iyong binuo sa wikang ito.

Ang landas sa pag-typecheck ng 4 na milyong linya ng Python code. Bahagi 1
Ang landas sa pag-typecheck ng 4 na milyong linya ng Python code. Bahagi 1

Pinagmulan: www.habr.com

Magdagdag ng komento