4 milyon sətirlik Python kodunu yazmağa gedən yol. 1-ci hissə

Bu gün biz Dropbox-un Python kodunun tip nəzarəti ilə necə məşğul olduğuna dair materialın tərcüməsinin birinci hissəsini diqqətinizə çatdırırıq.

4 milyon sətirlik Python kodunu yazmağa gedən yol. 1-ci hissə

Dropbox Python-da çox şey yazır. Bu, həm back-end xidmətlər, həm də masaüstü müştəri proqramları üçün çox geniş istifadə etdiyimiz dildir. Biz də Go, TypeScript və Rust-dan çox istifadə edirik, lakin Python bizim əsas dilimizdir. Bizim miqyamızı nəzərə alsaq və söhbət milyonlarla sətir Python kodundan gedir, məlum oldu ki, belə kodun dinamik şəkildə yazılması lazımsız yerə onun başa düşülməsini çətinləşdirib və əmək məhsuldarlığına ciddi təsir göstərməyə başlayıb. Bu problemi azaltmaq üçün kodumuzu mypy-dən istifadə edərək tədricən statik tip yoxlamasına keçirməyə başladıq. Bu, yəqin ki, Python üçün ən məşhur müstəqil tip yoxlama sistemidir. Mypy açıq mənbəli layihədir, onun əsas tərtibatçıları Dropbox-da işləyirlər.

Dropbox bu miqyasda Python kodunda statik tip yoxlanmasını həyata keçirən ilk şirkətlərdən biri idi. Mypy bu gün minlərlə layihədə istifadə olunur. Bu alət saysız-hesabsız, necə deyərlər, "döyüşdə sınaqdan keçirilmişdir". İndi olduğumuz yerə çatmaq üçün uzun bir yol qət etmişik. Bu yolda çoxlu uğursuz təşəbbüslər və uğursuz sınaqlar oldu. Bu yazı Python-da statik tip yoxlanışının tarixini, tədqiqat layihəmin bir hissəsi kimi çətin başlanğıclarından Python-da yazan saysız-hesabsız tərtibatçılar üçün tip yoxlanılması və tip işarələrinin adi hala çevrildiyi günümüzə qədər əhatə edir. Bu mexanizmlər indi IDE və kod analizatorları kimi bir çox alətlər tərəfindən dəstəklənir.

İkinci hissəni oxuyun

Növ yoxlaması nə üçün lazımdır?

Əgər siz nə vaxtsa dinamik şəkildə yazılmış Python-dan istifadə etmisinizsə, son vaxtlar statik yazma və mypy ətrafında niyə bu qədər hay-küy yarandığına dair çaşqınlıq yarana bilər. Və ya bəlkə siz Python-u dinamik yazmağa görə bəyənirsiniz və baş verənlər sadəcə sizi əsəbləşdirir. Statik yazmanın dəyərinin açarı həllərin miqyasıdır: layihəniz nə qədər böyükdürsə, statik yazmağa bir o qədər çox meyl edirsiniz və sonda ona həqiqətən ehtiyacınız olur.

Tutaq ki, müəyyən bir layihə on minlərlə sətir ölçüsünə çatdı və məlum oldu ki, onun üzərində bir neçə proqramçı işləyir. Təcrübəmizə əsaslanaraq oxşar layihəyə baxaraq deyə bilərik ki, onun kodunu başa düşmək tərtibatçıları məhsuldar saxlamaq üçün əsas olacaq. Növ annotasiyaları olmadan, məsələn, funksiyaya hansı arqumentlərin ötürülməsini və ya funksiyanın hansı növlərin qaytara biləcəyini anlamaq çətin ola bilər. Tip annotasiyalarından istifadə etmədən cavab vermək çox vaxt çətin olan tipik suallar bunlardır:

  • Bu funksiya geri qayıda bilər None?
  • Bu arqument nə olmalıdır? items?
  • Atribut növü nədir id: int bu, str, və ya bəlkə bəzi xüsusi növü?
  • Bu arqument siyahı olmalıdırmı? Ona bir dəst ötürmək mümkündürmü?

Aşağıdakı tipli şərhli kod parçasına baxsanız və oxşar suallara cavab verməyə çalışsanız, bunun ən sadə tapşırıq olduğu ortaya çıxır:

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

  • read_metadata qayıtmır None, çünki qaytarma növü deyil Optional[…].
  • arqument items xətlərin ardıcıllığıdır. Onu təsadüfi təkrarlamaq olmaz.
  • Atribut id bayt sətiridir.

İdeal bir dünyada, bütün bu cür incəliklərin daxili sənədlərdə (docstring) təsvir olunacağını gözləmək olardı. Ancaq təcrübə, işləməli olduğunuz kodda bu cür sənədlərin çox vaxt müşahidə edilmədiyinə dair çoxlu nümunələr verir. Kodeksdə belə sənədlər olsa belə, onun mütləq düzgünlüyünə inanmaq olmaz. Bu sənədlər qeyri-müəyyən, qeyri-dəqiq və anlaşılmazlıqlara açıq ola bilər. Böyük komandalarda və ya böyük layihələrdə bu problem son dərəcə kəskinləşə bilər.

Python layihələrin ilkin və ya aralıq mərhələlərində üstün olsa da, müəyyən nöqtədə uğurlu layihələr və Python-dan istifadə edən şirkətlər həyati sualla qarşılaşa bilərlər: “Hər şeyi statik olaraq yazılmış dildə yenidən yazmalıyıqmı?”.

mypy kimi tip yoxlama sistemləri yuxarıdakı problemi tərtibatçıya növləri təsvir etmək üçün rəsmi dillə təmin etməklə və bu tip bəyannamələrin proqramın icrasına uyğunluğunu yoxlamaqla (və isteğe bağlı olaraq onların mövcudluğunu yoxlamaqla) həll edir. Ümumiyyətlə, deyə bilərik ki, bu sistemlər bizim ixtiyarımıza diqqətlə yoxlanılmış sənədlər kimi bir şey verir.

Bu cür sistemlərin istifadəsi digər üstünlüklərə malikdir və onlar artıq tamamilə əhəmiyyətsizdir:

  • Tip yoxlama sistemi bəzi kiçik (və o qədər də kiçik olmayan) səhvləri aşkar edə bilər. Tipik bir nümunə, bir dəyəri emal etməyi unutduqları zamandır None və ya başqa bir xüsusi şərt.
  • Kodun refaktorinqi çox sadələşdirilmişdir, çünki tip yoxlama sistemi çox vaxt hansı kodun dəyişdirilməli olduğu barədə çox dəqiqdir. Eyni zamanda, testlərlə 100% kod əhatəsinə ümid etmək lazım deyil, hər halda, ümumiyyətlə mümkün deyil. Problemin səbəbini tapmaq üçün yığın izinin dərinliklərinə varmağa ehtiyac yoxdur.
  • Böyük layihələrdə belə, mypy tez-tez saniyənin bir hissəsində tam tip yoxlanışı edə bilər. Və testlərin icrası adətən onlarla saniyə və ya hətta dəqiqə çəkir. Tip yoxlama sistemi proqramçıya ani geribildirim verir və ona öz işini daha tez yerinə yetirməyə imkan verir. Kod testinin nəticələrini daha sürətli əldə etmək üçün ona daha kövrək və real obyektləri istehza və yamaqlarla əvəz edən vahid testləri yazmağa ehtiyac yoxdur.

PyCharm və ya Visual Studio Code kimi IDE-lər və redaktorlar tərtibatçılara kodu tamamlamaq, xətaları vurğulamaq və çox istifadə olunan dil konstruksiyalarına dəstək vermək üçün tipli annotasiyaların gücündən istifadə edir. Bunlar da yazmağın faydalarından yalnız bir neçəsidir. Bəzi proqramçılar üçün bütün bunlar yazmağın lehinə əsas arqumentdir. Bu, həyata keçirildikdən dərhal sonra fayda verən bir şeydir. Növlər üçün bu istifadə halı mypy kimi ayrıca bir növ yoxlama sistemi tələb etmir, baxmayaraq ki, mypy tip annotasiyalarını kodla uyğun saxlamağa kömək etdiyini qeyd etmək lazımdır.

Mypy fonu

Mypy-nin tarixi Böyük Britaniyada, Kembricdə, mən Dropbox-a qoşulmamışdan bir neçə il əvvəl başladı. Mən doktorluq tədqiqatımın bir hissəsi kimi statik tipli və dinamik dillərin birləşməsində iştirak etmişəm. Məni Jeremy Siek və Walid Taha-nın artımlı yazma haqqında məqaləsi və Typed Racket layihəsindən ilham aldım. Mən müxtəlif layihələr üçün eyni proqramlaşdırma dilindən istifadə etməyin yollarını tapmağa çalışdım - kiçik skriptlərdən tutmuş milyonlarla sətirdən ibarət kod bazalarına qədər. Eyni zamanda, hər hansı bir miqyaslı bir layihədə çox böyük kompromislərə getməməsini təmin etmək istədim. Bütün bunların mühüm hissəsi tipsiz prototip layihədən hərtərəfli sınaqdan keçirilmiş statik tipli hazır məhsula tədricən keçmək ideyası idi. Bu günlərdə bu ideyalar əsasən təbii qəbul edilir, lakin 2010-cu ildə bu, hələ də fəal şəkildə araşdırılan problem idi.

Tip yoxlamasında mənim orijinal işim Python-a yönəlməyib. Əvəzində kiçik bir “ev istehsalı” dildən istifadə etdim Alore. Nə haqqında danışdığımızı başa düşməyinizə imkan verəcək bir nümunə (növ qeydləri burada isteğe bağlıdır):

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

Sadələşdirilmiş ana dilindən istifadə elmi tədqiqatlarda istifadə olunan ümumi yanaşmadır. Bu, ən azı ona görə deyil ki, tez bir zamanda təcrübələr aparmağa imkan verir, həm də tədqiqatla heç bir əlaqəsi olmayan şeylərə asanlıqla məhəl qoyula bilməz. Real proqramlaşdırma dilləri mürəkkəb tətbiqləri olan geniş miqyaslı hadisələrə meyllidir və bu, təcrübəni ləngidir. Bununla belə, sadələşdirilmiş dilə əsaslanan istənilən nəticə bir az şübhəli görünür, çünki bu nəticələri əldə edərkən tədqiqatçı dillərin praktik istifadəsi üçün vacib olan mülahizələri qurban vermiş ola bilər.

Alore üçün tip yoxlayıcım çox perspektivli görünürdü, lakin mən onu real kodla sınaqdan keçirmək istədim, siz deyə bilərsiniz ki, Alore dilində yazılmayıb. Xoşbəxtlikdən mənim üçün Alore dili əsasən Python ilə eyni fikirlərə əsaslanırdı. Python-un sintaksisi və semantikası ilə işləyə bilməsi üçün yazı yoxlayıcısını yenidən düzəltmək kifayət qədər asan idi. Bu, bizə açıq mənbəli Python kodunda tip yoxlamasını sınamağa imkan verdi. Bundan əlavə, Alore dilində yazılmış kodu Python koduna çevirmək üçün transpiler yazdım və ondan yazım kodunu tərcümə etmək üçün istifadə etdim. İndi mənim Python-da yazılmış bir növ yoxlama sistemim var idi ki, bu da Python alt dəstini, o dilin bir növünü dəstəkləyir! (Alore üçün məna kəsb edən bəzi memarlıq qərarları Python üçün zəif uyğun idi və bu, mypy kod bazasının bəzi hissələrində hələ də nəzərə çarpır.)

Əslində, mənim tip sistemim tərəfindən dəstəklənən dil bu nöqtədə tam olaraq Python adlandırıla bilməz: Python 3 tipli annotasiya sintaksisinin bəzi məhdudiyyətləri səbəbindən Python-un bir variantı idi.

Java və Python qarışığına bənzəyirdi:

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

O vaxt mənim fikirlərimdən biri bu cür Python-u C-yə və ya bəlkə də JVM bayt koduna tərtib etməklə performansı yaxşılaşdırmaq üçün tip annotasiyalarından istifadə etmək idi. Mən kompilyator prototipini yazmaq mərhələsinə gəldim, lakin tip yoxlamasının özü olduqca faydalı göründüyü üçün bu fikri tərk etdim.

Mən layihəmi Santa Klarada PyCon 2013-də təqdim etdim. Bu barədə ömürlük xeyirxah Python diktatoru Guido van Rossum ilə də danışdım. O, məni öz sintaksisimi buraxmağa və standart Python 3 sintaksisinə sadiq qalmağa inandırdı. Python 3 funksiya annotasiyalarını dəstəkləyir, ona görə də mənim nümunəm aşağıda göstərildiyi kimi yenidən yazıla bilər, nəticədə normal Python proqramı yaranır:

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

Bəzi güzəştlərə getməli oldum (ilk növbədə qeyd etmək istəyirəm ki, öz sintaksisimi məhz bu səbəbdən icad etmişəm). Xüsusilə, o zaman dilin ən son versiyası olan Python 3.3 dəyişən annotasiyaları dəstəkləmirdi. Mən Guido ilə e-poçt vasitəsilə bu cür annotasiyaların sintaktik dizaynı üçün müxtəlif imkanları müzakirə etdim. Dəyişənlər üçün tip şərhlərindən istifadə etmək qərarına gəldik. Bu nəzərdə tutulan məqsədə xidmət etdi, lakin bir qədər çətin idi (Python 3.6 bizə daha gözəl sintaksis verdi):

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

Növ şərhləri, həmçinin tip annotasiyaları üçün daxili dəstəyi olmayan Python 2-ni dəstəkləmək üçün faydalı oldu:

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

Məlum oldu ki, bu (və digər) mübadilələrin əslində heç bir əhəmiyyəti yoxdur - statik yazmağın faydaları o demək idi ki, istifadəçilər idealdan daha az sintaksisi tezliklə unutdular. Növü yoxlanılan Python kodunda heç bir xüsusi sintaktik konstruksiyadan istifadə olunmadığından, mövcud Python alətləri və kod emal prosesləri normal işləməyə davam etdi və bu, tərtibatçıların yeni aləti öyrənməsini xeyli asanlaşdırdı.

Guido, həmçinin dissertasiyamı bitirdikdən sonra məni Dropbox-a qoşulmağa inandırdı. Mypy hekayəsinin ən maraqlı hissəsi burada başlayır.

Davam etmək üçün ...

Hörmətli oxucular! Python-dan istifadə edirsinizsə, bu dildə hazırladığınız layihələrin miqyası barədə bizə məlumat verin.

4 milyon sətirlik Python kodunu yazmağa gedən yol. 1-ci hissə
4 milyon sətirlik Python kodunu yazmağa gedən yol. 1-ci hissə

Mənbə: www.habr.com

Добавить комментарий