Путь к проверке типов 4 миллионов строк Python-кода. Часть 3

Автор оригинала: Jukka Lehtosalo
  • Перевод
Представляем вашему вниманию третью часть перевода материала о пути, который прошла компания Dropbox, внедряя у себя систему проверки типов Python-кода.



→ Предыдущие части: первая и вторая

Достижение 4 миллионов строк типизированного кода


Ещё одной важной задачей (это был вторая по популярности проблема, волновавшая тех, кто участвовал во внутренних опросах) было увеличение объёма кода в Dropbox, покрытого проверками типов. Мы испробовали несколько подходов для решения этой задачи — от естественного роста объёма типизированной кодовой базы до сосредоточения усилий членов команды mypy на статическом и динамическом автоматизированном выводе типов. В итоге создалось такое впечатление, что тут нет простой выигрышной стратегии, но мы смогли достичь быстрого роста объёма аннотированного кода, скомбинировав множество подходов.

В результате в нашем самом большом Python-репозитории (с бэкенд-кодом) число строк аннотированного кода достигло почти 4 миллионов. Работа по статической типизации кода была проведена примерно за три года. Mypy теперь поддерживает различные виды отчётов о покрытии кода типами, которые упрощают наблюдение за ходом типизации. В частности, мы можем формировать отчёты по коду с неопределённостями в типах, с такими, например, как явное использование типа Any в аннотациях, которые невозможно проверить, или с такими, как импорт сторонних библиотек, в которых нет аннотаций типов. Мы, в рамках проекта по повышению точности проверки типов в Dropbox, внесли вклад в улучшении определений типов (так называемых stub-файлов) для некоторых популярных опенсорсных библиотек в централизованном Python-репозитории typeshed.

Мы реализовали (и стандартизировали в последующих PEP) новые возможности системы типов, которые позволяют использовать более точные типы для некоторых специфичных Python-паттернов. Заметным примером этого является TypeDict, который предоставляет типы для JSON-подобных словарей, имеющих фиксированный набор строковых ключей, каждый из которых имеет значение собственного типа. Мы продолжим расширять систему типов. Вероятно, нашим следующим шагом станет улучшение поддержки возможностей Python по работе с числами.


Количество строк аннотированного кода: сервер


Количество строк аннотированного кода: клиент


Общее количество строк аннотированного кода

Вот обзор основных особенностей тех действий, которые мы выполнили ради повышения объёма аннотированного кода в Dropbox:

Строгость аннотирования. Мы постепенно повышали требования по строгости аннотирования нового кода. Мы начали с советов линтера, в которых предлагалось добавлять аннотации в файлы, в которых уже есть некоторые аннотации. Теперь мы требуем наличия аннотаций типов в новых Python-файлах и в большинстве существующих файлов.

Отчёты о типизации. Мы еженедельно рассылаем командам отчёты об уровне типизации их кода и даём советы, касающиеся того, что именно стоит аннотировать в первую очередь.

Популяризация mypy. Мы рассказываем о mypy на различных мероприятиях и общаемся с командами, помогая им начать пользоваться аннотациями типов.

Опросы. Мы проводим периодические опросы пользователей для выявления главных проблем. Мы готовы зайти достаточно далеко в деле решения этих проблем (вплоть до создания нового языка ради ускорения mypy!).

Производительность. Мы значительно улучшили производительность mypy благодаря использованию демона и mypyc. Сделано это ради сглаживания неудобств, возникающих в процессе аннотирования, и ради того, чтобы получить возможность работать с большими объёмами кода.

Интеграция с редакторами. Мы создали средства для поддержки запуска mypy в редакторах, которые пользуются популярностью в Dropbox. Сюда входят PyCharm, Vim и VS Code. Это значительно упростило процесс выполнения работ по аннотированию кода и по проверке его работоспособности. Подобные действия обычно характерны при аннотировании существующего кода.

Статический анализ. Мы создали инструмент для вывода сигнатур функций с использованием средств статического анализа. Этот инструмент может работать только в сравнительно простых ситуациях, но он помог нам без особых усилий увеличить покрытие кода типами.

Поддержка сторонних библиотек. Во многих наших проектах используется набор инструментов SQLAlchemy. В нём применяются динамические возможности Python, которые типы PEP 484 неспособны смоделировать напрямую. Мы, в соответствии с PEP 561, создали соответствующий stub-файл и написали плагин для mypy (опенсорсный), улучшающий поддержку SQLAlchemy.

Сложности, с которыми мы встретились


Путь к 4 миллионам строк типизированного кода не всегда давался нам легко. На этом пути мы встретили немало ям и сделали несколько ошибок. Вот некоторые из проблем, с которыми мы столкнулись. Надеемся, рассказ о них поможет другим подобных проблем избежать.

Пропущенные файлы. Мы начинали работу с проверки лишь небольшого объёма файлов. Всё, не входящее в число этих файлов, не проверялось. Файлы в список проверки добавлялись тогда, когда в них появлялись первые аннотации. Если что-то импортировалось из модуля, расположенного за пределами области проверки, то речь шла о работе со значениями типа Any, которые вообще не проверялись. Это привело к значительной потере точности типизации, особенно — на ранних стадиях миграции. Такой подход до сих пор работал на удивление хорошо, хотя типичной была ситуация, когда добавление файлов в область проверки выявляло проблемы в других частях кодовой базы. В самом худшем случае, когда объединялись две изолированных области кода, в которых, независимо друг от друга, типы были уже проверены, оказывалось, что типы этих областей несовместимы друг с другом. Это приводило к необходимости внесения в аннотации множества изменений. Теперь, оглядываясь назад, мы понимаем, что нам следовало бы как можно раньше добавить в область проверки типов mypy базовые библиотечные модули. Это сделало бы нашу работу гораздо более предсказуемой.

Аннотирование старого кода. Когда мы начинали работу, у нас было около 4 миллионов строк уже существующего Python-кода. Было ясно, что аннотирование всего этого кода — задача не из лёгких. Мы создали инструмент, названный PyAnnotate, который может собирать сведения о типах во время выполнения тестов и умеет добавлять в код аннотации типов, основываясь на собранных сведениях. Однако особенно широкого внедрения этого инструмента мы не заметили. Сбор сведений о типах был медленным, автоматически сгенерированные аннотации часто требовали множества ручных правок. Мы думали об автоматическом запуске этого средства при каждой проверке кода, или о том, чтобы собирать сведения о типах, основываясь на анализе некоего небольшого объёма реальных сетевых запросов, но решили этого не делать, так как любой из этих подходов слишком рискован.

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

Циклические импорты. Выше я говорил о циклических импортах (о «клубках зависимостей»), существование которых усложнило ускорение mypy. Нам, кроме того, пришлось серьёзно поработать над тем, чтобы снабдить mypy поддержкой всех видов идиом, причиной возникновения которых являются эти вот циклические импорты. Недавно мы завершили крупный проект по редизайну системы, который исправил большинство проблем mypy, касающихся циклических импортов. Эти проблемы, на самом деле, произрастали из весьма ранних дней проекта, ещё из Alore, учебного языка, на который был изначально ориентирован проект mypy. Синтаксис Alore позволяет легко решать проблемы циклических команд импорта. Современный mypy унаследовал некоторые ограничения от своей ранней бесхитростной реализации (которая отлично подходила для Alore). Python усложняет работу с циклическими импортами в основном из-за неоднозначности выражений. Например, в ходе операции присваивания значения может, на самом деле, определяться псевдоним типа. Mypy не всегда способен выявлять подобные вещи до тех пор, пока большая часть цикла импорта не будет обработана. В Alore таких неоднозначностей не было. Неудачные решения, принятые на ранних этапах разработки системы, могут преподнести программисту неприятный сюрприз через много лет.

Итоги: путь к 5 миллионам строк кода и к новым горизонтам


Проект mypy прошёл долгий путь — от ранних прототипов, до системы, средствами которой контролируются типы продакшн-кода объёмом в 4 миллиона строк. По ходу развития mypy была осуществлена стандартизация подсказок по типам в Python. В наши дни вокруг типизации Python-кода развилась мощная экосистема. В ней нашлось место поддержке библиотек, в ней присутствуют вспомогательные средства для IDE и редакторов, в ней имеются несколько систем контроля типов, у каждой из которых есть свои плюсы и минусы.

Несмотря на то, что проверка типов уже воспринимается в Dropbox как нечто само собой разумеющееся, я уверен в том, что мы всё ещё живём на заре типизации Python-кода. Я думаю, что технологии проверки типов будут продолжать развиваться и совершенствоваться.

Если вы ещё не пользовались проверками типов в своём крупномасштабном Python-проекте, то знайте, что сейчас — весьма подходящее время для того, чтобы начать переход на статическую типизацию. Я беседовал с теми, кто совершил подобный переход. Никто из них об этом не пожалел. Контроль типов превращает Python в язык, который гораздо лучше, чем «обычный Python», подходит для разработки больших проектов.

Уважаемые читатели! Пользуетесь ли вы контролем типов в своих Python-проектах?


RUVDS.com
1 472,72
RUVDS – хостинг VDS/VPS серверов
Поделиться публикацией

Похожие публикации

Комментарии 11

    0
    Если вы ещё не пользовались проверками типов в своём крупномасштабном Python-проекте, то знайте, что сейчас — весьма подходящее время для того, чтобы начать переход на статическую типизацию. Я беседовал с теми, кто совершил подобный переход. Никто из них об этом не пожалел. Контроль типов превращает Python в язык, который гораздо лучше, чем «обычный Python», подходит для разработки больших проектов.

    Непонятно, зачем тогда вообще выбирать Python. Особенно учитывая, что аннотации, судя по тексту статьи, требуются и для всех новых файлов, так что «быстрое прототипирование без типов» (что само по себе, мягко скажем, неоднозначно) не вариант.

      0
      Быстрое прототипирование без типов по-прежнему вариант, написали прототип, протестили, а перед мержем добавили аннотаций чтобы удовлетворить mypy и получить зеленую галку от CI.
      0
      Возможно я задам глупый вопрос, так как только начинаю свой путь в разработке, прошу не серчать, и не минусовать, а просто объяснить:
      зачем выбирать язык с динамической типизацией, а потом, по сути, приводить его к статической?
        0
        • Выбирают потому, что кажется, что не надо будет думать об этом (ошибаются, конечно — типы не куда не деваются, просто тогда их в уме держать надо).
        • На определенном этапе роста держать в уме эту метаинформацию не получается даже в отдельных участках кода.
        • Сам Python объявляет это своей необычайной гибкостью и свободой. Ну может быть оно и так. К тому же, система типов MyPy намного гибче, чем у многих распространенных более статических ЯП.
        • А еще обилие разных библиотек под Python, включая системные, научные и даже лучшие биндинги Qt.
          0
          у питона все хорошо с производительностью пока используешь функции основанные на си.
          • иногда в проекте появляется узкое место в производительности и тогда либо переписывать на си этот участок либо это.
          • + динамика да позволяет делает быстро многие вещи.
          • но когда идет поиск ошибок и рефактор вполне удобно пройти добавить типы когда не спешишь и менеджер уже не стоит за душой
          • Когда сам уже забыл какие параметры нужны твоей функции удобно видеть тип в подсказке. Конечно ты можешь передать параметры любых типов компиляция пройдет, но функция от этого не будет работать верно?



            0
            Все равно, исходя из сказанного, я категорически не понимаю — в чем преимущество (читай: «смысл») динамической типизации. Да, наверное, на коротких дистанциях — это дает некоторое преимущество в скорости разработки (все равно, не думаю, что это преимущество существенное), но ведь любой проект имеет свойство становиться больше и больше с течением времени, имеют свойство уходить старые разработчики, и приходить новые -> из всего этого следует, что выбор языка с динамической типизацией крайне недальновиден, и не имеет под собой никаких оснований.
            Если я ошибаюсь — опять же, прошу объяснить, ибо, честно, я понимаю, что могу чего-то не понимать.
              0
              Меньше порог вхождения, что позволяет студентам, ученым и devops программировать алгоритмы. Когда синтаксис простой проще сконцентрироваться на самом алгоритме и выше читаемость, а значит и проще разобраться, что он делает.
              Единственное надо указать на вход типы и набор параметров, чтобы легче было применить функцию без копания в ней вообще и все. А те кто захочет разобраться код будет читаем. А читаемость кода тоже очень важный момент при больших проектах, рефракторах и т.д.
              Если речь идет о веб то просто идет куча шаблонного кода выполнения запросов, где и так все понятно, там статика и не нужна.
              Си может что угодно от ОС, драйверов до сайтов. Но подождите сайты на си делать конешно можно, но Очень геморойно. По этому каждый язык на самом деле под свою задачу.
              Если тебе нужен овер стабильное и надежное мультиплатформенное приложение, или на андройд ты берешь JAVA. Если тебе надо чтобы программа супер быстро обрабатывала и ела немного ресурсов (особенно если нужно системить типо драйвер написать) ты берешь c/c++ (надеюсь что и rust добавится сюда), а если тебе надо сделать сайт, скрипт для бекапа, протестировать научный алгоритм, бота, парсер ты берешь питон, потому что это просто быстро и понятно! И то если вдруг у тебя узкое место ты можешь его переписать на си! Беспроигрышный вариант по моему мнению. Кроме того сам по себе как и доказывает статья питон не язык — это концепция языка. Он уже реализован десятками способов под разные оболочки в основном конечно на си, но и на java есть jpython. Вообще все потихоньку идет к универсальному языку в котором все моменты будут сбалансированы, на котором будет удобно делать и ОС и сайты. Даже называют питона прадедушкой такого языка.
              Вообщем если сильно заморочится можно сидеть и не слазить практически с любого языка, вопрос в том как эффективнее решить задачу?
              Питон решает эффективно очень широкий спектр задач, больше чем любой другой язык о котором я слышал, поэтому его учить и использовать эффективно. А эти все дополнения делают его применение ещё удобнее — если требуется.
              В остальных случаях да — полная статика, но статику я буду использовать только в тех случаях когда вообще никак по другому не достигнуть требуемых параметров программы. Читаемость и универсальность лично для меня важнее. Учитывая как он развился и сколько способов придумали как, и что ускорить это требуется все меньше.

              Вообщем я вижу современного прогера это питон + си + java (там где они действительно нужны).
                0
                Вообщем я вижу современного прогера это питон + си + java (там где они действительно нужны).

                iOS — не прогеры? ))
                  0
                  прогеры это как раз таки специфичная си подобная задача, если ты решил серьезно iOS зачем вообще сыр бор, правда? хотя в принципе движки тот же godot под ios компилятся, поэтому необязательно писать на яблочном языке. Тут всегда выгодно веб приложение оно работает везде )
                  0
                  Когда синтаксис простой проще сконцентрироваться на самом алгоритме и выше читаемость, а значит и проще разобраться, что он делает.

                  Наличие типизации не означает сложный синтаксис. А вот понять, что алгоритм не имеет смысла, потому что в типах ерунда, полезно.


                  а если тебе надо сделать сайт, скрипт для бекапа, протестировать научный алгоритм, бота, парсер ты берешь питон, потому что это просто быстро и понятно!

                  Лично я во всех этих случаях (и ещё некоторых) беру хаскель. Опыт (полутора соревнований из интереса) показывает, что ряд задач я при этом решаю быстрее и корректнее коллег.

              0
              Возможно я задам глупый вопрос, так как только начинаю свой путь в разработке, прошу не серчать, и не минусовать, а просто объяснить:
              зачем выбирать язык с динамической типизацией, а потом, по сути, приводить его к статической?


              Для прототипирования язык с динамической типизацией удобнее.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое