Hacker News

Параўнанне інструмента праверкі тыпаў Python: вывад пустога кантэйнера

Каментарыі

2 min read Via pyrefly.org

Mewayz Team

Editorial Team

Hacker News

Чаму пустыя кантэйнеры парушаюць шашкі тыпу Python — і што з гэтым можна зрабіць

Сістэма паступовага набору тэксту Python значна палепшылася пасля таго, як PEP 484 увёў падказкі па тыпах у 2015 годзе. Сёння мільёны распрацоўшчыкаў спадзяюцца на статычныя сродкі праверкі тыпаў, каб выяўляць памылкі, перш чым яны патрапяць у вытворчасць. Але ў сістэме тыпаў ёсць тонкі, непрыемны куток, які па-ранейшаму выклікае здзіўленне нават дасведчаных інжынераў: які тып мае пусты кантэйнер? Калі вы пішаце x = [] без анатацыі, ваша праграма праверкі тыпу павінна здагадвацца — і розныя прылады адгадваюць па-рознаму. Гэтае разыходжанне стварае рэальныя праблемы для каманд, якія падтрымліваюць вялікія кодавыя базы, дзе пераключэнне або камбінаванне сродкаў праверкі тыпаў можа за адну ноч выявіць сотні нечаканых памылак.

У гэтым артыкуле разбіраецца тое, як чатыры асноўныя сродкі праверкі тыпаў Python — mypy, pyright, pytype і pyre — апрацоўваюць выснову пра пусты кантэйнер, чаму яны не згодныя і якія практычныя стратэгіі вы можаце прыняць для напісання бяспечнага тыпу Python незалежна ад выбару інструментаў.

Асноўная праблема: пустыя кантэйнеры па сваёй сутнасці неадназначныя

Разгледзім гэты бяскрыўдны радок Python: results = []. Ці з'яўляецца вынікі спісам[int]? спіс[str]? Спіс[dict[str, Any]]? Без дадатковага кантэксту даведацца пра гэта немагчыма. Асяроддзе выканання Python не клапоціцца - спісы гетэрагенныя па сваёй прыродзе - але праверкі статычнага тыпу павінны прызначыць канкрэтны тып кожнай зменнай, каб выконваць сваю працу. Гэта стварае фундаментальнае напружанне паміж дынамічнай гнуткасцю Python і гарантыямі, якія спрабуе даць статычны аналіз.

Праблема складаецца са слоўнікаў і набораў. Пусты {} фактычна аналізуецца як dict, а не set, што дадае сінтаксічную неадназначнасць у дадатак да неадназначнасці ўзроўню тыпу. А ўкладзеныя кантэйнеры — напрыклад, defaultdict(list) або results = {k: [] for k in keys} — даводзяць механізмы вываду да сваіх магчымасцей. Кожная праграма праверкі тыпаў распрацавала сваю ўласную эўрыстыку, і адрозненні больш істотныя, чым мяркуе большасць распрацоўшчыкаў.

У вытворчых сістэмах, якія апрацоўваюць рэальныя працоўныя нагрузкі - няхай гэта будзе CRM, які апрацоўвае запісы кліентаў, модуль выстаўлення рахункаў, які генеруе пазіцыі, або канвеер аналітыкі, які аб'ядноўвае паказчыкі, - пустыя кантэйнеры пастаянна з'яўляюцца як шаблоны ініцыялізацыі. Памылковыя тыпы не толькі ствараюць папярэджанні Linter; ён можа маскіраваць сапраўдныя памылкі, якія праскокваюць у час выканання.

Mypy: адкладзенае выснова з імпліцытным любым

Mypy, найстарэйшая і найбольш распаўсюджаная праграма праверкі тыпаў Python, выкарыстоўвае адносна паблажлівы падыход да пустых кантэйнераў. Калі ён сустракае x = [] у вобласці дзеяння функцыі, ён спрабуе адкласці рашэнне тыпу і вызначыць тып элемента з наступнага выкарыстання. Калі вы напішаце x = [] пасля x.append(42), mypy вывядзе list[int]. Гэтая стратэгія "аб'яднання" працуе на здзіўленне добра ў простых выпадках, калі кантэйнер запаўняецца ў той жа вобласці.

Аднак паводзіны mypy значна змяняюцца ў залежнасці ад кантэксту і налад строгасці. У вобласці дзеяння модуля (код верхняга ўзроўню) або калі кантэйнер перадаецца іншай функцыі перад запаўненнем, mypy часта вяртаецца да list[Any]. Пад сцягам --strict гэта выклікае памылку, але ў рэжыме па змаўчанні яна бясшумна праходзіць. Гэта азначае, што каманды, якія запускаюць mypy без строгага рэжыму, могуць назапашваць дзясяткі кантэйнераў з няяўнай тыпізацыяй, якія дзейнічаюць як люкі для выхаду з сістэмы тыпаў, перашкоджваючы яе прызначэнню.

Адно асабліва тонкае паводзіны: версіі mypy да 0.990 часам выводзяць list[Unknown] унутрана, а потым пашыраюць да list[Any] пры прызначэнні. Пасля 0.990 выснова была больш жорсткай, але змяненне зламала дзіўную колькасць рэальных кодавых баз, якія абапіраліся на дазвольныя паводзіны, не разумеючы гэтага. Гэта перыядычная тэма — змены ў выснове аб пустым кантэйнеры з'яўляюцца аднымі з самых разбуральных абнаўленняў праверкі тыпаў, таму што шаблоны настолькі паўсюдныя.

Pyright: строгі вывад і тып «Невядомы»

Pyright, распрацаваны Microsoft і які працуе на Pylance ў VS Code, займае прынцыпова іншую філасофскую пазіцыю. Замест таго, каб моўчкі вяртацца да Любы, pyright адрознівае Невядомы (тып, які яшчэ не вызначаны) і Любы (яўная адмова ад праверкі тыпу). Калі вы пішаце x = [] у строгім рэжыме pyright, ён выводзіць спіс[Невядома] і паведамляе пра дыягностыку, прымушаючы вас даць анатацыю.

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

  • x = [] за якім ідзе x.append("прывітанне") — аўтарскае права прадугледжвае list[str]
  • x = [] затым x.append(1) затым x.append("прывітанне") — аўтарскае права робіць вывад list[int | str]
  • x = [] перадаецца непасрэдна ў функцыю, якая чакае list[int] — pyright выводзіць list[int] з кантэксту сайта выкліку
  • x = [] вяртаецца з функцыі без анатацыі тыпу вяртання — pyright паведамляе пра памылку, а не адгадвае

Гэты двухнакіраваны вывад (з выкарыстаннем наступнага выкарыстання і чаканых тыпаў з сайтаў выклікаў) робіць pyright значна больш дакладным, чым mypy для пустых кантэйнераў. Кампраміс заключаецца ў шматслоўнасці: строгі рэжым pyright адзначае прыкладна на 30-40% больш праблем у звычайнай неанатаванай кодавай базе ў параўнанні са строгім рэжымам mypy, паводле аналізу некалькіх справаздач аб міграцыі з адкрытым зыходным кодам. Для каманд, якія ствараюць складаныя бэкэнд-сістэмы — скажам, платформу, якая кіруе 207 узаемазвязанымі модулямі, якія ахопліваюць CRM, налічэнне заработнай платы і аналітыку — строгасць pyright улоўлівае тонкія неадпаведнасці інтэрфейсу, якія паблажлівыя высновы прапусцяць.

Pytype і Pyre: менш наведвальныя дарогі

Pytype Google прытрымліваецца, магчыма, найбольш прагматычнага падыходу. Замест таго, каб патрабаваць анатацыі або вяртацца да Любы, pytype выкарыстоўвае аналіз усёй праграмы, каб адсочваць, як кантэйнер выкарыстоўваецца праз межы функцый. Калі вы ствараеце пусты спіс у адной функцыі і перадаеце яго іншай, якая дадае цэлыя лікі, pytype часта можа вывесці list[int] без аніякіх анатацый. Гэты крос-функцыянальны вывад з'яўляецца дарагім з пункту гледжання вылічэнняў - pytype значна павольней, чым mypy або pyright на вялікіх кодавых базах - але ён стварае менш ілжывых спрацоўванняў на неанатаваным кодзе.

Pytype таксама ўводзіць канцэпцыю "частковых тыпаў" для пустых кантэйнераў. Свежаствораны [] атрымлівае частковы тып, які паступова ўдасканальваецца па меры большага выкарыстання праверкі. Гэта канцэптуальна элегантна, але можа ствараць заблытаныя паведамленні пра памылкі, калі частковы тып не можа быць цалкам вырашаны, напрыклад, калі пусты кантэйнер праходзіць праз некалькі функцый без запаўнення.

💡 DID YOU KNOW?

Mewayz replaces 8+ business tools in one platform

CRM · Invoicing · HR · Projects · Booking · eCommerce · POS · Analytics. Free forever plan available.

Start Free →

Вогнішча Meta, тым часам, набліжаецца да паводзін mypy, але з больш жорсткімі наладамі па змаўчанні. Pyre разглядае x = [] як спіс[невядома] і патрабуе анатацыі ў большасці кантэкстаў. Чым pyre адрозніваецца, так гэта апрацоўкай пустых літаралаў слоўніка, якія выкарыстоўваюцца як kwargs — звычайнага шаблону ў вэб-структурах. Pyre мае логіку асаблівых выпадкаў, каб выводзіць тыпы слоўнікаў з кантэкстаў аргументаў ключавых слоў, памяншаючы нагрузку на анатацыі ў кодавых базах з вялікай колькасцю фрэймворкаў. Улічваючы, што большасць сучасных вэб-прыкладанняў прадугледжвае інтэнсіўнае выкарыстанне распакавання слоўніка для канфігурацыі і апрацоўкі запытаў, гэты прагматызм прыносіць свае дывідэнды.

Уздзеянне ў рэальным свеце: калі разыходжанне высновы ўкусы

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

<цытата>

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

Канкрэтны прыклад: каманда фінтэх-стартапа паведаміла, што выдаткавала тры дні на адладку вытворчай праблемы, дзе пусты спіс, ініцыялізаваны ў функцыі апрацоўкі плацяжоў, быў выведзены mypy як list[Any]. Спіс павінен быў утрымліваць аб'екты Decimal для валютных сум, але шлях кода дабаўляў замест гэтага значэнні float. Паблажлівая выснова Mypy моўчкі дазволіла гэта. Памылка выявілася толькі тады, калі памылкі акруглення ў арыфметыцы з плаваючай часткай выклікалі разыходжанне ў 0,01 даляра ў партыі з 12 000 рахункаў-фактур. Калі б яны выкарыстоўвалі аўтарскае права ў строгім рэжыме або проста анатавалі пусты спіс як спіс[дзесятковы], памылка была б выяўлена падчас распрацоўкі.

У Mewayz, дзе платформа апрацоўвае выстаўленне рахункаў, разлік заработнай платы і фінансавую аналітыку для больш чым 138 000 уліковых запісаў карыстальнікаў, такі разрыў у бяспецы тыпаў не з'яўляецца тэарэтычным - гэта розніца паміж правільным налічэннем заработнай платы і дарагім пераразлікам. Строгая дысцыпліна ўводу тэксту вакол ініцыялізацыі кантэйнера - адна з тых "сумных" інжынерных практык, якія прадухіляюць захапляльныя вытворчыя інцыдэнты.

Найлепшыя практыкі ініцыялізацыі абарончага кантэйнера

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

  1. Заўсёды пазначайце пустыя зменныя кантэйнера. Напішыце вынікі: спіс[int] = [] замест вынікі = []. Нязначныя выдаткі на шматслоўнасць нязначныя ў параўнанні з зэканомленым часам адладкі. Гэтая адзіная практыка ліквідуе прыкладна 80% праблем высновы аб пустым кантэйнеры.
  2. Выкарыстоўвайце фабрычныя функцыі для складаных кантэйнераў. Замест cache = {} напішыце функцыю накшталт def make_cache() -> dict[str, list[UserRecord]]: return {}. Анатацыя вяртанага тыпу робіць прызначаны тып адназначным і самадакументаваным.
  3. Аддавайце перавагу тыпізаваным канструктарам перад літэраламі для нетрывіяльных тыпаў. Напішыце items: set[int] = set(), а не спадзявацца на вывад разумення мноства. Для defaultdict і Counter заўсёды давайце параметр тыпу: counts: Counter[str] = Counter().
  4. Наладзьце строгі рэжым вашай праверкі тыпу для новага кода. І mypy, і pyright падтрымліваюць канфігурацыю для кожнага файла або каталога. Уключыць строгую праверку новых модуляў пры паступовым пераносе старога кода. Гэта прадухіляе назапашванне новых няяўна тыпізаваных кантэйнераў.
  5. Дадайце параўнанне сродку праверкі тыпаў у канвеер CI. Запуск як mypy, так і pyright на вашай кодавай базе рана выяўляе разыходжанні ў выснове. Калі шаблон праходзіць адну праверку, але не праходзіць іншую, гэта сігнал, што тып недастаткова выразны.

Шырокая карціна: праверка тыпу як камандная практыка

Вывад аб пустым кантэйнеры - гэта ў канчатковым выніку мікрасвет большай праблемы ў сістэме тыпаў Python: напружанне паміж зручнасцю і бяспекай. Філасофія Python «мы ўсе дарослыя па згодзе» выдатна працуе для стварэння прататыпаў і сцэнарыяў, але вытворчыя сістэмы, якія абслугоўваюць тысячы карыстальнікаў, патрабуюць больш моцных гарантый. Той факт, што чатыры асноўныя сродкі праверкі тыпаў разыходзяцца ў меркаваннях адносна такой асноўнай рэчы, як тып [], падкрэслівае, што экасістэма тыпізацыі Python усё яшчэ стаіць.

Для каманд інжынераў, якія ствараюць складаныя платформы — незалежна ад таго, кіруеце вы некалькімі мікрасэрвісамі або інтэграванай сістэмай з сотнямі ўзаемазвязаных модуляў, такіх як бізнес-АС Mewayz — практычныя парады простыя: не спадзявайцеся на высновы для пустых кантэйнераў, абярыце сродак праверкі тыпаў і строга яго наладзьце, а анатацыі тыпаў разглядайце як дакументацыю, якую можна праверыць машынай. Пяць хвілін, выдаткаваных на напісанне list[Invoice] замест [], зэканомяць вам гадзіны адладкі, калі ваша кодавая база будзе маштабавацца.

Паколькі PEP 696 (параметры тыпу па змаўчанні) і PEP 695 (сінтаксіс параметраў тыпу) працягваюць прымяняцца ў новых версіях Python, эрганоміка відавочнага ўводу будзе пастаянна паляпшацца. Разрыў паміж «анатаваным» і «неанатаваным» Python будзе звужацца. Але да таго дня выразныя тыпы кантэйнераў застаюцца адной з найбольш рэнтабельных практык у наборы інструментаў распрацоўшчыка Python — невялікая дысцыпліна, якая плаціць складаныя працэнты за кожны модуль, кожны спрынт і кожнае вытворчае разгортванне.

Стварыце сваю бізнес-АС сёння

Ад фрылансераў да агенцтваў, Mewayz падтрымлівае больш за 138 000 прадпрыемстваў з дапамогай 207 інтэграваных модуляў. Пачніце бясплатна, абнаўляйце па меры росту.

Стварыць бясплатны ўліковы запіс →

Часта задаюць пытанні

Чаму сродкі праверкі тыпу не могуць узгадніць тып пустога спісу?

Калі вы пішаце `x = []`, сродак праверкі тыпаў павінен выводзіць тып без відавочных падказак. Розныя шашкі выкарыстоўваюць розныя стратэгіі: адны выводзяць `list[Any]` (спіс чаго заўгодна), а іншыя могуць выводзіць больш канкрэтны, але няправільны тып, напрыклад `list[None]`. Гэта адсутнасць універсальнага стандарту, чаму яны не згодныя. Для праектаў, якія выкарыстоўваюць некалькі шашак, гэтая неадпаведнасць можа быць сур'ёзным галаўным болем, парушаючы аналіз у адным інструменце і пераходзячы ў іншы.

Які самы просты спосаб выправіць памылкі пустога кантэйнера?

Самае простае рашэнне - даць відавочную анатацыю тыпу. Замест `my_list = []` напішыце `my_list: list[str] = []`, каб яўна аб'явіць прызначаны тып. Гэта выдаляе ўсю неадназначнасць для праверкі тыпаў, забяспечваючы паслядоўныя паводзіны розных інструментаў, такіх як mypy, Pyright і Pyre. Гэтая практыка рэкамендуецца для ўсіх ініцыялізацый пустых кантэйнераў, каб прадухіліць памылкі вываду.

Як мне апрацоўваць пустыя кантэйнеры ў азначэннях класаў?

Гэта распаўсюджаная праблема, таму што анатацыі ўнутры класаў патрабуюць асаблівай апрацоўкі. Вы павінны выкарыстоўваць імпарт `from __future__ import annotations` або анатацыю `ClassVar`, калі спіс павінен быць атрыбутам класа. Напрыклад, `class MyClass: my_list: ClassVar[list[str]] = []`. Без гэтага сродку праверкі тыпаў можа быць складана правільна вызначыць тып, што прывядзе да памылак.

Ці існуюць інструменты, якія дапамагаюць справіцца з праблемамі набору тэксту ў вялікіх праектах?

Так, прасунутыя сродкі праверкі тыпаў, такія як Pyright (які забяспечвае Pylance ў VS Code), асабліва добрыя ў апрацоўцы складаных высноў. Для вялікіх кодавых баз такія платформы, як Mewayz (прапануе 207 модуляў аналізу за 19 долараў у месяц), могуць забяспечыць больш глыбокую і паслядоўную праверку тыпаў і дапамагчы ўкараніць практыку анатавання ва ўсёй вашай камандзе, змякчаючы неадпаведнасці, якія абмяркоўваюцца ў артыкуле.

Try Mewayz Free

All-in-one platform for CRM, invoicing, projects, HR & more. No credit card required.

Start managing your business smarter today

Join 30,000+ businesses. Free forever plan · No credit card required.

Ready to put this into practice?

Join 30,000+ businesses using Mewayz. Free forever plan — no credit card required.

Start Free Trial →

Ready to take action?

Start your free Mewayz trial today

All-in-one business platform. No credit card required.

Start Free →

14-day free trial · No credit card · Cancel anytime