Шлях да праверкі тыпаў 4 радкоў Python-кода. Частка 1

Сёння мы прапануем вашай увазе першую частку перакладу матэрыялу аб тым, як у Dropbox займаюцца кантролем тыпаў Python-кода.

Шлях да праверкі тыпаў 4 радкоў Python-кода. Частка 1

У Dropbox шмат пішуць на Python. Гэта – мова, якую мы выкарыстоўваем надзвычай шырока – як для бэкэнд-сэрвісаў, так і для настольных кліенцкіх прыкладанняў. Яшчэ мы ў вялікіх аб'ёмах ужываем Go, TypeScript і Rust, але Python - гэта нашая галоўная мова. Калі ўлічваць нашы маштабы, а гаворка ідзе аб мільёнах радкоў Python-кода, аказалася, што дынамічная тыпізацыя такога кода неапраўдана ўскладніла яго разуменне і пачала сур'ёзна ўплываць на прадуктыўнасць працы. Для змякчэння гэтай праблемы мы прыступілі да паступовага пераводу нашага кода на статычную праверку тыпаў з выкарыстаннем mypy. Гэта, верагодна, самая папулярная самастойная сістэма праверкі тыпаў для Python. Mypy – гэта апенсорсны праект, яго асноўныя распрацоўшчыкі працуюць у Dropbox.

Dropbox аказалася адной з першых кампаній, якая ўкараніла статычную праверку тыпаў у Python-кодзе ў падобным маштабе. У наш час mypy выкарыстоўваецца ў тысячах праектаў. Гэты інструмент незлічоную колькасць разоў, што называецца, "правераны ў баі". Нам, для таго, каб дабрацца туды, дзе мы знаходзімся зараз, прыйшлося прайсці доўгі шлях. На гэтым шляху было нямала няўдалых пачынанняў і якія праваліліся эксперыментаў. Гэты матэрыял апавядае пра гісторыю статычнай праверкі тыпаў у Python - з самага яе няпростага пачатку, якое было часткай майго навуковага даследчага праекта, да сённяшняга дня, калі праверкі тыпаў і падказкі па тыпах сталі звыклымі для незлічонай колькасці распрацоўшчыкаў, якія пішуць на Python. Гэтыя механізмы зараз падтрымліваюцца мноствам прылад – такіх, як IDE і аналізатары кода.

Чытаць другую частку

Навошта патрэбная праверка тыпаў?

Калі вы калі-небудзь карысталіся дынамічна тыпізаваным Python - у вас можа паўстаць некаторае неразуменне таго, чаму вакол статычнай тыпізацыі і mypy у апошні час падняўся такі шум. А можа быць і так, што Python вам падабаецца менавіта з-за яго дынамічнай тыпізацыі, а тое, што адбываецца папросту вас хвалюе. Ключ да каштоўнасці статычнай тыпізацыі - гэта маштаб рашэнняў: чым больш ваш праект - тым мацней вы схіляецеся да статычнай тыпізацыі, і, у рэшце рэшт, тым мацней вам гэта па-сапраўднаму трэба.

Выкажам здагадку, нейкі праект дасягнуў памераў у дзясяткі тысяч радкоў, і аказалася, што над ім працуюць некалькі праграмістаў. Разглядаючы падобны праект мы, засноўваючыся на нашым досведзе, можам сказаць, што разуменне яго кода стане ключом да падтрымкі прадуктыўнасці распрацоўнікаў. Без анатацый тыпаў няпроста бывае высветліць, напрыклад, тое, якія аргументы трэба перадаць функцыі, ці тое, значэнні якіх тыпаў можа нейкая функцыя вяртаць. Вось тыповыя пытанні, на якія часта нялёгка бывае адказаць без ужывання анатацый тыпаў:

  • Ці можа гэтая функцыя вярнуць None?
  • Чым павінен быць гэты аргумэнт items?
  • Які тып атрыбуту id: int Ці гэта, str, ці, можа, які-небудзь карыстацкі тып?
  • Ці павінен гэты аргумент быць спісам? Ці можна перадаць у яго картэж?

Калі зірнуць на наступны фрагмент кода, забяспечаны анатацыямі тыпаў, і паспрабаваць адказаць на падобныя пытанні, тое апынецца, што гэта - найпростая задача:

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

  • read_metadata не вяртае None, так як які вяртаецца тып не з'яўляецца Optional[…].
  • аргумент items - Гэта паслядоўнасць радкоў. Яе нельга ітэраваць у адвольным парадку.
  • Атрыбут id - гэта радок байтаў.

У ідэальным свеце можна было б чакаць, што ўсе падобныя тонкасці будуць апісаны ва ўбудаванай дакументацыі (docstring). Але досвед дае масу прыкладаў таго, што падобнай дакументацыі ў кодзе, з якім даводзіцца працаваць, часта не назіраецца. Нават калі такая дакументацыя ў кодзе і прысутнічае, нельга разлічваць на яе абсалютную правільнасць. Гэтая дакументацыя можа быць смутнай, недакладнай, якая пакідае масу магчымасцяў для яе няправільнага разумення. У вялікіх камандах ці ў вялікіх праектах гэтая праблема можа стаць вельмі вострай.

Хоць Python выдатна паказвае сябе на ранніх ці прамежкавых стадыях праектаў, у пэўны момант паспяховыя праекты і кампаніі, якія выкарыстоўваюць Python, могуць сутыкнуцца з жыццёва важным пытаннем: "Ці трэба нам перапісаць усё на статычна тыпізаванай мове?".

Сістэмы праверкі тыпаў накшталт mypy вырашаюць вышэйадзначаную праблему дзякуючы таму, што падаюць у распараджэнне распрацоўніка фармальную мову для апісання тыпаў, і таму, што правяраюць тое, каб апісанні тыпаў адпавядалі б рэалізацыі праграм (і, што неабавязкова, правяраюць іх існаванне). У цэлым можна сказаць, што гэтыя сістэмы даюць у наша распараджэнне нешта накшталт старанна праверанай дакументацыі.

У прымянення падобных сістэм ёсць і іншыя перавагі, і яны ўжо зусім нетрывіяльныя:

  • Сістэма праверкі тыпаў можа выявіць некаторыя дробныя (а гэтак жа - і не асоба дробныя) памылкі. Тыповы прыклад - гэта калі забываюць апрацаваць значэнне None ці нейкая іншая асаблівая ўмова.
  • Значна спрашчаецца рэфактарынг кода, бо сістэма праверкі тыпаў часта вельмі сапраўды паведамляе пра тое, які код трэба змяніць. Пры гэтым нам не трэба спадзявацца на 100% пакрыццё кода тэстамі, што, у любым выпадку, звычайна немагчыма. Нам не трэба вывучаць глыбіні справаздач трасіроўкі стэка для таго, каб высветліць прычыну непаладкі.
  • Нават у вялікіх праектах mypy часта можа правесці поўную праверку тыпаў за долі секунды. А выкананне тэстаў звычайна займае дзясяткі секунд ці нават хвіліны. Сістэма праверкі тыпаў дае праграмісту імгненную зваротную сувязь і дазваляе яму хутчэй рабіць сваю справу. Яму не трэба больш пісаць далікатныя і цяжкія ў падтрымцы модульныя тэсты, якія замяняюць рэальныя сутнасці мокамі і патчамі толькі дзеля таго, каб хутчэй атрымаць вынікі выпрабаванняў кода.

IDE і рэдактары, такія, як PyCharm або Visual Studio Code, выкарыстоўваюць магчымасці анатацый тыпаў для прадастаўлення распрацоўнікам магчымасцяў па аўтаматычным завяршэнні кода, па падсвятленні памылак, па падтрымцы часта выкарыстоўваных моўных канструкцый. І гэта - толькі некаторыя з плюсаў, якія дае тыпізацыя. Для некаторых праграмістаў усё гэта - галоўны аргумент на карысць тыпізацыі. Гэта тое, што прыносіць карысць адразу ж пасля ўкаранення ў працу. Гэты варыянт выкарыстання тыпаў не патрабуе ўжывання асобнай сістэмы праверкі тыпаў, такі як mypy, хоць трэба адзначыць, што mypy дапамагае падтрымліваць адпаведнасць анатацый тыпаў і кода.

Перадгісторыя mypy

Гісторыя mypy пачалася ў Вялікабрытаніі, у Кембрыджы, за некалькі гадоў да таго, як я далучыўся да Dropbox. Я займаўся ў рамках правядзення доктарскага даследавання пытаннем уніфікацыі статычна тыпізаваных і дынамічных моў. Мяне натхняў артыкул аб паступовай тыпізацыі Джэрэмі Сіека і Валіда Таха, а таксама праект Typed Racket. Я спрабаваў знайсці спосабы выкарыстання адной і той жа мовы праграмавання для розных праектаў - ад маленькіх скрыптоў, да кодавых баз, якія складаюцца з многіх мільёнаў радкоў. Пры гэтым мне хацелася, каб у праекце любога маштабу не прыйшлося б ісці на занадта вялікія кампрамісы. Важнай часткай усяго гэтага была ідэя аб паступовым пераходзе ад нетыпізаванага прататыпа праекту да ўсебакова пратэставанаму статычна тыпізаванаму гатоваму прадукту. У нашыя дні гэтыя ідэі, у значнай ступені, прымаюцца як належнае, але ў 2010 годзе гэта была праблема, якую ўсё яшчэ актыўна даследавалі.

Мая першапачатковая праца ў галіне праверкі тыпаў не была накіравана на Python. Замест яго я выкарыстоўваў маленькую «самаробную» мову. Alore. Вось прыклад, які дазволіць вам зразумець - пра што ідзе гаворка (анатацыі тыпаў тут неабавязковыя):

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

Выкарыстанне спрошчанай мовы ўласнай распрацоўкі – гэта звычайны падыход, які прымяняецца ў навуковых даследаваннях. Гэта так не ў апошнюю чаргу з-за таго, што такое дазваляе хутка праводзіць эксперыменты, а таксама з-за таго, што тое, што да даследавання дачынення не мае, можна бесперашкодна ігнараваць. Рэальна выкарыстоўваюцца мовы праграмавання звычайна ўяўляюць сабой маштабныя з'явы са складанымі рэалізацыямі, а гэта эксперыменты запавольвае. Аднак любыя вынікі, заснаваныя на спрошчанай мове, выглядаюць крыху падазронымі, бо пры атрыманні гэтых вынікаў даследчык, можа быць, ахвяраваў меркаваннямі, важнымі для практычнага выкарыстання моў.

Мой сродак праверкі тыпаў для Alore выглядаў вельмі шматабяцальным, але мне жадалася праверыць яго, выканаўшы эксперыменты з рэальным кодам, якога, можна сказаць, на Alore напісана не было. Да майго шчасця, мова Alore у значнай ступені была заснавана на тых жа ідэях, што і Python. Было дастаткова проста перарабіць сродак для праверкі тыпаў так, каб яно магло б працаваць з сінтаксісам і семантыкай Python. Гэта дазволіла паспрабаваць выканаць праверку тыпаў у апенсорсным Python-кодзе. Акрамя таго, я напісаў транспайлер для пераўтварэння кода, напісанага на Alore у Python-код і выкарыстоўваў яго для трансляцыі кода майго сродку для праверкі тыпаў. Цяпер у мяне была сістэма для праверкі тыпаў, напісаная на Python, якая падтрымлівала падмноства Python, нейкую разнавіднасць гэтай мовы! (Пэўныя архітэктурныя рашэнні, якія мелі сэнс для Alore, дрэнна падыходзілі для Python, гэта ўсё яшчэ прыкметна ў некаторых частках кодавай базы mypy.)

Насамрэч, мова, якая падтрымліваецца маёй сістэмай тыпаў, у гэты момант не зусім можна было назваць Python: гэта быў варыянт Python з-за некаторых абмежаванняў сінтаксісу анатацый тыпаў Python 3.

Выглядала гэта як сумесь Java і Python:

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

Адна з маіх ідэй у той час складалася ў тым, каб выкарыстоўваць анатацыі тыпаў для паляпшэння прадукцыйнасці шляхам кампіляцыі гэтай разнавіднасці Python у C, ці, магчыма, у байт-код JVM. Я прасунуўся да стадыі напісання прататыпа кампілятара, але пакінуў гэтую задуму, бо праверка тыпаў і сама па сабе выглядала дастаткова карыснай.

Я, у выніку, прадставіў мой праект на канферэнцыі PyCon 2013 у Санта-Клары. Гэтак жа я пагаварыў пра гэта з Гвіда ван Рассумам, з вялікадушным пажыццёвым дыктатарам Python. Ён пераканаў мяне адмовіцца ад уласнага сінтаксісу і прытрымлівацца стандартнага сінтаксісу Python 3. Python 3 падтрымлівае анатацыі функцый, у выніку мой прыклад можна было перапісаць так, як паказана ніжэй, атрымаўшы звычайную Python-праграму:

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

Мне спатрэбілася пайсці на некаторыя кампрамісы (у першую чаргу хачу адзначыць, што я вынайшаў уласны сінтаксіс менавіта таму). У прыватнасці, Python 3.3, самая свежая версія мовы на той момант, не падтрымліваў анатацый зменных. Я абмеркаваў з Гвіда па электроннай пошце розныя магчымасці сінтаксічнага афармлення падобных анатацый. Мы вырашылі выкарыстоўваць для зменных каментары з указаннем тыпаў. Гэта дазваляла дамагчыся пастаўленай мэты, але выглядала некалькі грувастка (Python 3.6 даў нам прыямнейшы сінтаксіс):

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

Каментары з тыпамі, акрамя таго, спатрэбіліся для падтрымкі Python 2, у якім няма ўбудаванай падтрымкі анатацый тыпаў:

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

Апынулася, што гэтыя (і іншыя) кампрамісы, насамрэч, не мелі адмысловага значэння - перавагі статычнай тыпізацыі прывялі да таго, што карыстачы хутка забыліся аб не суцэль ідэальным сінтаксісе. Бо зараз у Python-кодзе, у якім кантраляваліся тыпы, не ўжываліся адмысловыя сінтаксічныя канструкцыі, існыя Python-прылады і працэсы па апрацоўцы кода працягнулі нармальна працаваць, што значна палегчыла засваенне распрацоўнікамі новай прылады.

Гвіда, акрамя таго, пераканаў мяне далучыцца да Dropbox пасля таго, як я абараніў выпускную працу. Тут пачынаецца самае цікавае ў гісторыі mypy.

Працяг будзе…

Паважаныя чытачы! Калі вы карыстаецеся Python - просім распавесці аб тым, праекты якога маштабу вы распрацоўваеце на гэтай мове.

Шлях да праверкі тыпаў 4 радкоў Python-кода. Частка 1
Шлях да праверкі тыпаў 4 радкоў Python-кода. Частка 1

Крыніца: habr.com

Дадаць каментар