{"id":38479,"date":"2019-10-31T22:23:56","date_gmt":"2019-10-31T19:23:56","guid":{"rendered":"https:\/\/prohoster.info\/blog\/put-k-proverke-tipov-4-millionov-strok-python-koda-chast-2\/"},"modified":"2019-10-31T22:23:56","modified_gmt":"2019-10-31T19:23:56","slug":"put-k-proverke-tipov-4-millionov-strok-python-koda-chast-2","status":"publish","type":"post","link":"https:\/\/prohoster.info\/ro\/blog\/administrirovanie\/put-k-proverke-tipov-4-millionov-strok-python-koda-chast-2","title":{"rendered":"Calea c\u0103tre verificarea tipurilor a 4 milioane de linii de cod Python. Partea 2","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Ast\u0103zi public\u0103m a doua parte a traducerii materialului despre cum Dropbox a organizat controlul tipurilor pentru c\u00e2teva milioane de linii de cod Python.<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/ruvds\/blog\/468235\/\"><img decoding=\"async\" alt=\"Calea c\u0103tre verificarea tipurilor a 4 milioane de linii de cod Python. Partea 2\" src=\"\/wp-content\/uploads\/2019\/09\/3f8f396d724153a0374a3fadca190d55.jpeg\" style=\"display:block;margin: 0 auto;\" \/><\/a><\/noindex><\/p>\n<p>\u2192 <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/ruvds\/blog\/468233\/\">Cite\u0219te prima parte<\/a><\/noindex><br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Suport oficial pentru tipuri (PEP 484)<\/h2>\n<p>\nAm desf\u0103\u0219urat primele experimente serioase cu mypy \u00een Dropbox \u00een timpul Hack Week 2014. Hack Week este un eveniment organizat de Dropbox pe parcursul unei s\u0103pt\u0103m\u00e2ni. \u00cen aceast\u0103 perioad\u0103, angaja\u021bii pot lucra la orice doresc! Unele dintre cele mai renumite proiecte tehnologice Dropbox au \u00eenceput \u00een cadrul acestor evenimente. \u00cen urma acestui experiment, am ajuns la concluzia c\u0103 mypy arat\u0103 promi\u021b\u0103tor, de\u0219i acest proiect nu era \u00eenc\u0103 preg\u0103tit pentru utilizare pe scar\u0103 larg\u0103.<\/p>\n<p>Pe atunci, circula ideea standardiz\u0103rii sistemelor de oferire a sugestiilor de tipuri Python. A\u0219a cum am men\u021bionat, \u00eencep\u00e2nd cu Python 3.0, se putea utiliza anot\u0103ri de tip pentru func\u021bii, dar acestea erau doar expresii arbitrare, f\u0103r\u0103 un sintax\u0103 \u0219i semantica definite. \u00cen timpul execu\u021biei programului, aceste anot\u0103ri erau, \u00een mare parte, pur \u0219i simplu ignorate. Dup\u0103 Hack Week, am \u00eenceput s\u0103 lucr\u0103m la standardizarea semantica. Aceast\u0103 munc\u0103 a dus la apari\u021bia <noindex><a rel=\"nofollow\" href=\"https:\/\/www.python.org\/dev\/peps\/pep-0484\/\">PEP 484<\/a><\/noindex> (la redactarea acestui document, au colaborat Guido van Rossum, Lukasz Langa \u0219i cu mine).<\/p>\n<p>Motivele noastre pot fi privite din dou\u0103 perspective. \u00cen primul r\u00e2nd, speram c\u0103 \u00eentregul ecosistem Python ar putea accepta o abordare comun\u0103 \u00een utilizarea sugestiilor de tip (type hints \u2014 termen folosit \u00een Python ca echivalent al \u201eanot\u0103rilor de tip\u201d). Asta, av\u00e2nd \u00een vedere riscurile posibile, ar fi fost mai bine dec\u00e2t utilizarea mai multor abord\u0103ri reciproc incompatibile. \u00cen al doilea r\u00e2nd, am dorit s\u0103 discut\u0103m deschis despre mecanismele de anotare a tipurilor cu numero\u0219i reprezentan\u021bi ai comunit\u0103\u021bii Python. Par\u021bial, aceast\u0103 dorin\u021b\u0103 a fost dictat\u0103 de faptul c\u0103 nu ne-ar fi pl\u0103cut s\u0103 p\u0103rem \u201er\u0103t\u0103ci\u021bi\u201d \u00een fa\u021ba ideilor fundamentale ale limbajului \u00een fa\u021ba unui public larg de programatori Python. Este un limbaj dinamic tipizat, cunoscut pentru \u201etipizarea prin ra\u021b\u0103\u201d. \u00cen comunitate, la \u00eenceput, nu a putut s\u0103 nu apar\u0103 o oarecare suspiciune fa\u021b\u0103 de ideea tipiz\u0103rii statice. Dar aceast\u0103 atitudine s-a atenuat \u00een cele din urm\u0103 \u2014 dup\u0103 ce a devenit clar c\u0103 tipizarea static\u0103 nu se inten\u021bioneaz\u0103 s\u0103 fie obligatorie (\u0219i dup\u0103 ce oamenii au \u00een\u021beles c\u0103 este cu adev\u0103rat util\u0103).<\/p>\n<p>Sintaxa adoptat\u0103 pentru tipuri a fost foarte asem\u0103n\u0103toare cu cea sus\u021binut\u0103 de mypy la acea vreme. Documentul PEP 484 a fost lansat \u00eempreun\u0103 cu Python 3.5 \u00een 2015. Python nu mai era doar un limbaj care sus\u021binea tipizarea dinamic\u0103. \u00cemi place s\u0103 consider acest eveniment ca o etap\u0103 semnificativ\u0103 \u00een istoria Python.<\/p>\n<h2>\u00cenceperea migr\u0103rii<\/h2>\n<p>\nLa sf\u00e2r\u0219itul anului 2015, a fost format un grup de trei persoane la Dropbox pentru a lucra la mypy. Ace\u0219tia erau Guido van Rossum, Greg Price \u0219i David Fisher. De atunci, situa\u021bia a \u00eenceput s\u0103 evolueze foarte rapid. Prima provocare \u00een calea cre\u0219terii mypy a fost performan\u021ba. A\u0219a cum am sugerat mai sus, \u00een perioada timpurie de dezvoltare a proiectului, m-am g\u00e2ndit s\u0103 traduc implementarea mypy \u00een limbajul C, dar aceast\u0103 idee a fost deocamdat\u0103 scoas\u0103 din lista de op\u021biuni. Am r\u0103mas bloca\u021bi folosind interpretorul CPython, care nu se remarc\u0103 printr-o vitez\u0103 suficient\u0103 pentru instrumente de tipul mypy. (Proiectul PyPy, o implementare alternativ\u0103 a Python cu un compilator JIT, nu ne-a ajutat nici el.)<\/p>\n<p>Din fericire, ne-au venit \u00een ajutor unele \u00eembun\u0103t\u0103\u021biri algoritmice. Prima \u201eaccelerare\u201d puternic\u0103 a fost implementarea verific\u0103rii incrementale. Ideea acestei \u00eembun\u0103t\u0103\u021biri a fost simpl\u0103: dac\u0103 toate dependen\u021bele modulului de la ultima execu\u021bie a mypy nu s-au schimbat, atunci putem utiliza, \u00een timpul lucrului cu dependen\u021bele, datele stocate \u00een cache \u00een timpul sesiunii anterioare. Era suficient s\u0103 facem verificarea tipurilor \u00een fi\u0219ierele modificate \u0219i \u00een acele fi\u0219iere care depindeau de ele. Mypy a mers chiar pu\u021bin mai departe: dac\u0103 interfa\u021ba extern\u0103 a modulului nu s-a schimbat, mypy considera c\u0103 nu trebuie s\u0103 verifice din nou modulele care import\u0103 acest modul.<\/p>\n<p>Verificarea incremental\u0103 ne-a ajutat mult \u00een documentarea unor volume mari de cod existent. Procesul implic\u0103, de obicei, numeroase execu\u021bii iterativ ale mypy, deoarece ad\u0103ug\u0103m gradual anot\u0103rile \u00een cod \u0219i le \u00eembun\u0103t\u0103\u021bim treptat. Prima execu\u021bie a mypy a fost totu\u0219i foarte lent\u0103, deoarece a fost necesar\u0103 verificarea multor dependen\u021be. Atunci, pentru a \u00eembun\u0103t\u0103\u021bi situa\u021bia, am implementat un mecanism de cache remote. Dac\u0103 mypy detecteaz\u0103 c\u0103 cache-ul local este probabil \u00eenvechit, acesta \u00eencarc\u0103 o instantaneu a cache-ului pentru \u00eentreaga baz\u0103 de cod dintr-un depozit centralizat. Apoi, efectueaz\u0103 o verificare incremental\u0103 folosind aceast\u0103 instantaneu. Acesta a fost un pas semnificativ \u00een sporirea performantelor mypy.<\/p>\n<p>A fost o perioad\u0103 de adoptare rapid\u0103 \u0219i natural\u0103 a sistemului de verificare a tipurilor \u00een Dropbox. La sf\u00e2r\u0219itul anului 2016, aveam deja aproximativ 420.000 de linii de cod Python cu anot\u0103ri de tipuri. Mul\u021bi utilizatori au primit cu entuziasm verificarea tipurilor. \u00cen Dropbox, mypy era utilizat din ce \u00een ce mai mult de echipele de dezvoltare.<\/p>\n<p>Totul p\u0103rea bine atunci, dar mai aveam multe de f\u0103cut. Am \u00eenceput s\u0103 realiz\u0103m sondaje interne periodice pentru a identifica problemele proiectului \u0219i pentru a \u00een\u021belege care \u00eentreb\u0103ri trebuie rezolvate \u00een primul r\u00e2nd (aceast\u0103 practic\u0103 este utilizat\u0103 \u0219i \u00een companie \u0219i ast\u0103zi). Cele mai importante, a\u0219a cum s-a dovedit, erau dou\u0103 sarcini. Prima - era nevoie de o acoperire mai mare a codului cu tipuri, a doua - era nevoie ca mypy s\u0103 func\u021bioneze mai repede. Era evident c\u0103 munca noastr\u0103 pentru accelerarea mypy \u0219i integrarea acestuia \u00een proiectele companiei era departe de a fi finalizat\u0103. Con\u0219tientiz\u00e2nd pe deplin importan\u021ba acestor dou\u0103 sarcini, ne-am apucat de rezolvarea lor.<\/p>\n<h2>Mai mult\u0103 performan\u021b\u0103!<\/h2>\n<p>\nVerific\u0103rile incrementale au accelerat mypy, dar acest instrument \u00eenc\u0103 nu era suficient de rapid. Multe dintre verific\u0103rile incrementale durau \u00een jur de un minut. Cauza acestei situa\u021bii erau importurile ciclice. Acest lucru probabil nu va surprinde pe cei care au lucrat cu baze de cod mari scrise \u00een Python. Aveam seturi de sute de module, fiecare dintre ele import\u00e2nd indirect toate celelalte. Dac\u0103 un fi\u0219ier dintr-un ciclu de importuri era modificat, mypy trebuia s\u0103 proceseze toate fi\u0219ierele din acel ciclu \u0219i, adesea, \u0219i orice module care importau module din acel ciclu. Unul dintre aceste cicluri era celebra \u201eghem\u0103 de dependen\u021be\u201d, care a cauzat multe nepl\u0103ceri la Dropbox. O dat\u0103, aceast\u0103 structur\u0103 con\u021binea c\u00e2teva sute de module, iar multe teste le importau, direct sau indirect, iar aceasta era utilizat\u0103 \u0219i \u00een codul de produc\u021bie.<\/p>\n<p>Am analizat posibilitatea de a \u201edescurca\u201d dependen\u021bele ciclice, dar nu aveam resurse pentru a face acest lucru. Erau prea multe coduri cu care nu eram familiariza\u021bi. \u00cen cele din urm\u0103, am ales o abordare alternativ\u0103. Am decis s\u0103 facem ca mypy s\u0103 func\u021bioneze rapid chiar \u0219i \u00een prezen\u021ba \u201eghemelor de dependen\u021be\u201d. Am realizat acest lucru printr-un demon mypy. Demonul este un proces de server care implementeaz\u0103 dou\u0103 caracteristici interesante. \u00cen primul r\u00e2nd, el p\u0103streaz\u0103 \u00een memorie informa\u021biile despre \u00eentreaga baz\u0103 de cod. Acest lucru \u00eenseamn\u0103 c\u0103 la fiecare rulare mypy nu trebuie s\u0103 \u00eencarce datele cache, referitoare la mii de dependen\u021be importate. \u00cen al doilea r\u00e2nd, el analizeaz\u0103 cu aten\u021bie, la nivelul unit\u0103\u021bilor structurale mici, dependen\u021bele dintre func\u021bii \u0219i alte entit\u0103\u021bi. De exemplu, dac\u0103 o func\u021bie <code>foo<\/code> apeleaz\u0103 func\u021bia <code>bar<\/code>, atunci exist\u0103 o dependen\u021b\u0103 <code>foo<\/code> de la <code>bar<\/code>. C\u00e2nd un fi\u0219ier este modificat, demonul proceseaz\u0103 mai \u00eent\u00e2i, \u00een izolare, doar fi\u0219ierul modificat. Apoi analizeaz\u0103 modific\u0103rile acestui fi\u0219ier, vizibile din exterior, cum ar fi semn\u0103turile de func\u021bie schimbate. Demonul folose\u0219te informa\u021bii detaliate despre importuri doar pentru a verifica din nou func\u021biile care folosesc cu adev\u0103rat func\u021bia modificat\u0103. De obicei, cu aceast\u0103 abordare, trebuie verificat un num\u0103r foarte mic de func\u021bii.<\/p>\n<p>Implementarea tuturor acestor elemente a fost o sarcin\u0103 dificil\u0103, deoarece implementarea ini\u021bial\u0103 a mypy a fost puternic orientat\u0103 spre procesarea unui singur fi\u0219ier deodat\u0103. A trebuit s\u0103 ne confrunt\u0103m cu numeroase situa\u021bii limit\u0103, apari\u021bia c\u0103rora necesita verific\u0103ri repetate \u00een cazurile \u00een care ceva se schimba \u00een cod. De exemplu, se \u00eent\u00e2mpl\u0103 a\u0219a atunci c\u00e2nd unei clase i se atribuie o nou\u0103 clas\u0103 de baz\u0103. Dup\u0103 ce am realizat ceea ce ne doream, am reu\u0219it s\u0103 reducem timpul de execu\u021bie al majorit\u0103\u021bii verific\u0103rilor incrementale la c\u00e2teva secunde. Ni se p\u0103rea o mare victorie.<\/p>\n<h2>\u00cenc\u0103 mai mult\u0103 performan\u021b\u0103!<\/h2>\n<p>\n\u00cempreun\u0103 cu cache-ul de la distan\u021b\u0103 despre care am vorbit mai sus, demonul mypy a rezolvat practic complet problemele ap\u0103rute atunci c\u00e2nd un programator ruleaz\u0103 frecvent verificarea tipurilor, f\u0103c\u00e2nd modific\u0103ri \u00een c\u00e2teva fi\u0219iere. Cu toate acestea, performan\u021ba sistemului \u00een cea mai pu\u021bin favorabil\u0103 situa\u021bie de utilizare era \u00eenc\u0103 departe de a fi optim\u0103. O pornire curat\u0103 a mypy putea dura mai mult de 15 minute. \u0218i aceasta era mult mai mult dec\u00e2t ne-am fi dorit. \u00cen fiecare s\u0103pt\u0103m\u00e2n\u0103 situa\u021bia devenea din ce \u00een ce mai grav\u0103, deoarece programatorii continuau s\u0103 scrie cod nou \u0219i s\u0103 adauge anota\u021bii la codul existent. Utilizatorii no\u0219tri \u00eenc\u0103 t\u00e2njeau dup\u0103 mai mult\u0103 performan\u021b\u0103 \u0219i noi eram bucuro\u0219i s\u0103 le venim \u00een \u00eent\u00e2mpinare.<\/p>\n<p>Am decis s\u0103 revenim la una dintre ideile anterioare legate de mypy. Mai precis, la transformarea codului Python \u00een cod C. Experimentele cu Cython (un sistem care permite transcrierea codului scris \u00een Python \u00een cod C) nu ne-au adus vreo accelerare vizibil\u0103, a\u0219a c\u0103 am decis s\u0103 revigor\u0103m ideea de a scrie propriul compilator. Deoarece baza de cod mypy (scris\u0103 \u00een Python) con\u021binea deja toate tipurile necesare de adnot\u0103ri, ne-a p\u0103rut o \u00eencercare demn\u0103 s\u0103 folosim aceste adnot\u0103ri pentru a accelera sistemul. Am creat rapid un prototip pentru a verifica aceast\u0103 idee. Acesta a ar\u0103tat, \u00een diverse micro-benchmarks, o cre\u0219tere a performan\u021bei de peste 10 ori. Ideea noastr\u0103 era s\u0103 compil\u0103m modulele Python \u00een module C prin intermediul Cython \u0219i s\u0103 transform\u0103m adnot\u0103rile de tip \u00een verific\u0103ri de tip efectuate \u00een timpul execu\u021biei programului (de obicei, adnot\u0103rile de tip sunt ignorate \u00een timpul execu\u021biei programelor \u0219i sunt utilizate doar de sistemele de verificare a tipurilor). Practic, pl\u0103nuim s\u0103 traducem implementarea mypy din Python \u00eentr-o limb\u0103 care a fost creat\u0103 pentru a fi tipizat\u0103 static, care ar avea un aspect (\u0219i, \u00een mare parte, func\u021bionare) exact ca Python. (Aceast\u0103 form\u0103 de migra\u021bie \u00eentre limbaje a devenit oarecum o tradi\u021bie a proiectului mypy. Implementarea ini\u021bial\u0103 a mypy a fost scris\u0103 \u00een Alore, ulterior fiind un hibrid sintactic \u00eentre Java \u0219i Python).<\/p>\n<p>Orientarea pe API-urile extensiilor CPython a fost cheia pentru a nu pierde oportunit\u0103\u021bile de gestionare a proiectului. Nu a fost necesar s\u0103 implement\u0103m o ma\u0219in\u0103 virtual\u0103 sau orice biblioteci de care ar fi avut nevoie mypy. \u00cen plus, toat\u0103 ecosistemul Python ne-ar fi fost \u00eenc\u0103 accesibil, ar fi fost disponibile toate instrumentele (cum ar fi pytest). Acest lucru a \u00eensemnat c\u0103 am putut continua s\u0103 folosim cod Python interpretat pe parcursul dezvolt\u0103rii, ceea ce ne-ar fi permis s\u0103 continu\u0103m s\u0103 lucr\u0103m, folosind o schem\u0103 foarte rapid\u0103 de modificare a codului \u0219i testare, f\u0103r\u0103 a a\u0219tepta compilarea codului. A p\u0103rut c\u0103 ne descurc\u0103m bine, a\u0219a-zic\u00e2nd, s\u0103 st\u0103m pe dou\u0103 scaune, \u0219i ne-a pl\u0103cut asta.<\/p>\n<p>Compilatorul pe care l-am numit mypyc (deoarece folose\u0219te mypy pentru analiza tipurilor, ca frontend) s-a dovedit a fi un proiect foarte de succes. \u00cen general, am realizat o accelerare de aproximativ 4 ori a lans\u0103rilor frecvente ale mypy f\u0103r\u0103 a utiliza caching. Dezvoltarea nucleului proiectului mypyc a durat o echip\u0103 mic\u0103, format\u0103 din Michael Sullivan, Ivan Levki\u021bki, Hugh Han \u0219i mine, aproximativ 4 luni calendaristice. Acest volum de lucru a fost mult mai mic dec\u00e2t cel necesar pentru a rescrie mypy, de exemplu, \u00een C++ sau Go. De asemenea, schimb\u0103rile pe care a fost nevoie s\u0103 le facem \u00een proiect au fost mult mai pu\u021bine dec\u00e2t ar fi trebuit s\u0103 facem dac\u0103 l-am fi rescris \u00eentr-o alt\u0103 limb\u0103. \u00cen plus, ne-am bazat pe faptul c\u0103 vom putea aduce mypyc la un nivel \u00een care al\u021bi programatori de la Dropbox s\u0103 \u00eel poat\u0103 folosi pentru a compila \u0219i accelera codul lor.<\/p>\n<p>Pentru a atinge un asemenea nivel de performan\u021b\u0103, a trebuit s\u0103 aplic\u0103m unele solu\u021bii inginerie interesante. Astfel, compilatorul poate accelera execu\u021bia multor opera\u021bii prin utilizarea unor construc\u021bii C rapide la nivel jos. De exemplu, apelul unei func\u021bii compilate este tradus \u00eentr-un apel C. Un astfel de apel se execut\u0103 mult mai repede dec\u00e2t un apel al unei func\u021bii interpretate. Unele opera\u021bii, cum ar fi c\u0103ut\u0103rile \u00een dic\u021bionare, \u00eenc\u0103 s-au redus la utilizarea apelurilor standard C-API din CPython, care, dup\u0103 compilare, s-au dovedit a fi doar pu\u021bin mai rapide. Am reu\u0219it s\u0103 elimin\u0103m sarcina suplimentar\u0103 pe sistem generat\u0103 de interpretare, dar acest lucru a adus doar un c\u00e2\u0219tig mic \u00een ceea ce prive\u0219te performan\u021ba.<\/p>\n<p>Pentru a identifica cele mai frecvente opera\u021bii \u201elente\u201d, am realizat profilarea codului. \u00cembun\u0103t\u0103\u021bi\u021bi datele ob\u021binute, am \u00eencercat fie s\u0103 ajust\u0103m mypyc pentru a genera un cod C mai rapid pentru astfel de opera\u021bii, fie s\u0103 rescriem codul Python relevant folosind opera\u021bii mai rapide (uneori nu aveam pur \u0219i simplu o solu\u021bie simpl\u0103 pentru o problem\u0103 sau alta). Rescrierea codului Python s-a dovedit adesea a fi o solu\u021bie mai u\u0219oar\u0103 dec\u00e2t implementarea unei transform\u0103ri automate a aceea\u0219i \u00een compilator. Pe termen lung, ne doream s\u0103 automatiz\u0103m multe dintre aceste transform\u0103ri, dar \u00een acel moment ne-am concentrat pe accelerarea mypy-ului cu un minim de efort. \u0218i, pe parcursul acestei obiective, am f\u0103cut c\u00e2teva compromisuri.<\/p>\n<p>Continuarea urmeaz\u0103...<\/p>\n<p><b>Stima\u021bi cititori!<\/b> Ce impresii v-a l\u0103sat proiectul mypy c\u00e2nd a\u021bi aflat despre existen\u021ba sa?<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/ruvds.com\/vps_start\/\"><img decoding=\"async\" alt=\"Calea c\u0103tre verificarea tipurilor a 4 milioane de linii de cod Python. Partea 2\" src=\"\/wp-content\/uploads\/2019\/09\/e3c1dd40228b56d0196fd65e4df22a59.jpeg\" style=\"display:block;margin: 0 auto;\" \/><\/a><\/noindex><br \/>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/ruvds.com\/ru-rub\/#order\"><img decoding=\"async\" alt=\"Calea c\u0103tre verificarea tipurilor a 4 milioane de linii de cod Python. Partea 2\" src=\"\/wp-content\/uploads\/2019\/09\/263173dd3ab487da330ff9063e3c180e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><\/a><\/noindex><br \/>\n<br \/>Sursa: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/ruvds\/blog\/468235\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0443\u0435\u043c \u0432\u0442\u043e\u0440\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0430 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0430 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u0432 Dropbox \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u043b\u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c \u0442\u0438\u043f\u043e\u0432 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043c\u0438\u043b\u043b\u0438\u043e\u043d\u043e\u0432 \u0441\u0442\u0440\u043e\u043a Python-\u043a\u043e\u0434\u0430. \u2192 \u0427\u0438\u0442\u0430\u0442\u044c \u043f\u0435\u0440\u0432\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u041e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u0442\u0438\u043f\u043e\u0432 (PEP 484) \u041c\u044b \u043f\u0440\u043e\u0432\u0435\u043b\u0438 \u043f\u0435\u0440\u0432\u044b\u0435 \u0441\u0435\u0440\u044c\u0451\u0437\u043d\u044b\u0435 \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u044b \u0441 mypy \u0432 Dropbox \u0432\u043e \u0432\u0440\u0435\u043c\u044f Hack Week 2014. Hack Week \u2014 \u044d\u0442\u043e \u043c\u0435\u0440\u043e\u043f\u0440\u0438\u044f\u0442\u0438\u0435, \u043f\u0440\u043e\u0432\u043e\u0434\u0438\u043c\u043e\u0435 Dropbox \u0432 \u0442\u0435\u0447\u0435\u043d\u0438\u0435 \u043e\u0434\u043d\u043e\u0439 \u043d\u0435\u0434\u0435\u043b\u0438. \u0412 \u044d\u0442\u043e \u0432\u0440\u0435\u043c\u044f [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":28879,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-38479","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0443\u0435\u043c \u0432\u0442\u043e\u0440\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0430 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0430 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u0432 Dropbox \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u043b\u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c \u0442\u0438\u043f\u043e\u0432 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043c\u0438\u043b\u043b\u0438\u043e\u043d\u043e\u0432 \u0441\u0442\u0440\u043e\u043a Python-\u043a\u043e\u0434\u0430.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/ro\/blog\/administrirovanie\/put-k-proverke-tipov-4-millionov-strok-python-koda-chast-2\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"ro_RO\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u041f\u0443\u0442\u044c \u043a \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0435 \u0442\u0438\u043f\u043e\u0432 4 \u043c\u0438\u043b\u043b\u0438\u043e\u043d\u043e\u0432 \u0441\u0442\u0440\u043e\u043a Python-\u043a\u043e\u0434\u0430. \u0427\u0430\u0441\u0442\u044c 2 | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0443\u0435\u043c \u0432\u0442\u043e\u0440\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0430 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0430 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u0432 Dropbox \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u043b\u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c \u0442\u0438\u043f\u043e\u0432 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043c\u0438\u043b\u043b\u0438\u043e\u043d\u043e\u0432 \u0441\u0442\u0440\u043e\u043a Python-\u043a\u043e\u0434\u0430.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/ro\/blog\/administrirovanie\/put-k-proverke-tipov-4-millionov-strok-python-koda-chast-2\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-10-31T19:23:56+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-10-31T19:23:56+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Calea c\u0103tre verificarea tipurilor \u00een 4 milioane de linii de cod Python. Partea 2 | ProHoster","description":"Ast\u0103zi public\u0103m a doua parte a traducerii materialului despre cum Dropbox a organizat controlul tipurilor pentru c\u00e2teva milioane de linii de cod Python.","canonical_url":"https:\/\/prohoster.info\/ro\/blog\/administrirovanie\/put-k-proverke-tipov-4-millionov-strok-python-koda-chast-2","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"ro_RO","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u041f\u0443\u0442\u044c \u043a \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0435 \u0442\u0438\u043f\u043e\u0432 4 \u043c\u0438\u043b\u043b\u0438\u043e\u043d\u043e\u0432 \u0441\u0442\u0440\u043e\u043a Python-\u043a\u043e\u0434\u0430. \u0427\u0430\u0441\u0442\u044c 2 | ProHoster","og:description":"\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0443\u0435\u043c \u0432\u0442\u043e\u0440\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0430 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0430 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u0432 Dropbox \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u043b\u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c \u0442\u0438\u043f\u043e\u0432 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043c\u0438\u043b\u043b\u0438\u043e\u043d\u043e\u0432 \u0441\u0442\u0440\u043e\u043a Python-\u043a\u043e\u0434\u0430.","og:url":"https:\/\/prohoster.info\/ro\/blog\/administrirovanie\/put-k-proverke-tipov-4-millionov-strok-python-koda-chast-2","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2019-10-31T19:23:56+00:00","article:modified_time":"2019-10-31T19:23:56+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"38479","title":null,"description":null,"keywords":null,"keyphrases":{"focus":[],"additional":[]},"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":"2026-01-23 22:13:19","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-03-01 01:08:32","updated":"2026-08-11 12:50:31","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/posts\/38479","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/comments?post=38479"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/posts\/38479\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/media\/28879"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/media?parent=38479"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/categories?post=38479"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/tags?post=38479"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}