Быстрее и проще: как изменился процесс разработки в Python 3.7

1
15123
Добавить в избранное

В июне 2018 года вышел Python 3.7. Часть изменений напрямую затрагивает рабочий процесс программиста. Кодить стало проще? Делимся впечатлениями.

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

Быстрее и проще: как изменился процесс разработки в Python 3.7

breakpoint: отладка и не только

Разумеется, мы все мечтаем писать идеальный код, но давайте взглянем правде в глаза: это фантастика. Отладка – неотъемлемый и необходимый этап разработки. В Python 3.7 появилась функция breakpoint(), которая может облегчить дебаггинг. Это не радикально новая возможность, а просто синтаксический сахар над имеющимися конструкциями.

Напишем вот такой подозрительный код и поместим его в файл bugs.py:

Сразу после запуска ловим ZeroDivisionError внутри функции divide(). Конечно, эту ошибку мы сделали целенаправленно, но все же постараемся ее отследить. Для этого нужно остановить программу в самом начале divide, то есть поставить брейкпоинт. Обозначим его так:

Встретив точку останова в коде, интерпретатор затормозит процесс исполнения, что позволит нам детально разобраться в происходящем. Как именно поставить брейкпоинт? В предыдущих версиях языка нам приходилось использовать вот эту магическую конструкцию:

Здесь мы импортируем стандартный дебаггер pdb и используем его для отслеживания потока выполнения. В Python 3.7 мы заменяем его на более короткую команду breakpoint():

Под капотом происходит ровно то же, что и в нашем первом примере: импорт pdb и обращение к методу set_trace(). Выходит, что польза функции breakpoint() заключается только в ее краткости? Разумеется, выигрыш существенный: 12 символов против 27. Однако реальный профит новой команды – широкие возможности для настройки.

Добавьте breakpoint() в файл bugs.py и запустите его:

Как только скрипт достигнет точки останова, он прекратит выполнение и запустит отладочную PDB-сессию. Чтобы прервать ее и вернуться, нажмите C и Enter. Чтобы лучше разобраться с отладкой и функциональностью PDB, обратитесь к этому руководству.

Настройка отладчика

Итак, вы сделали какие-то отладочные действия и думаете, что исправили ошибку. Теперь нужно снова запустить программу, но так, чтобы она не останавливалась на дебаггере. Разумеется, можно его просто закомментировать, но есть более удобный способ. Для управления отладкой используется переменная окружения PYTHONBREAKPOINT. Если присвоить ей значение 0, то все точки останова в коде будут проигнорированы интерпретатором:

Хм, кажется, баг мы все еще не пофиксили…

С помощью PYTHONBREAKPOINT можно даже заменить PDB на другой отладчик, например, PuDB:

Не забудьте установить pudb, чтобы пример заработал:

pip install pudb

Импорт модуля Python берет на себя.

Таким образом, появилась возможность установить собственную функцию отладки. Нужно лишь присвоить ее переменной окружения PYTHONBREAKPOINT. Если вы не знаете, как это сделать, посмотрите здесь.

Вызов любых функций

Но breakpoint необязательно должен быть отладчиком. Например, с его помощью можно запустить сессию IPython:

Вы даже можете установить свою функцию, которую будет вызывать команда breakpoint(). Например, этот код печатает локальные переменные. Поместите его в bp_utils.py:

А теперь обновите значение PYTHONBREAKPOINT, используя нотацию <модуль>.<функция>:

Обычно breakpoint вызывает функции, которые не принимают параметры, но никто не запрещает их передавать. Давайте исправим наш отладчик в bugs.py:

Заметьте, если используется отладчик PDB, который установлен по умолчанию, вы получите TypeError, так как его метод set_trace() не принимает параметров.

Поэтому давайте заменим функцию отладки на print(), чтобы понять, как это работает:

Вы можете узнать больше о breakpoint() и sys.breakpointhook() из PEP 553.

Ускоренная типизация и предварительное объявление

Рекомендации типов улучшались во всех релизах Python 3. Теперь система типизации работает довольно стабильно. Последнее обновление также не обошло эту область стороной: добавилась поддержка ядра, в разы увеличилась производительность, появилось предварительное объявление.

Интерпретатор Python во время выполнения программы не проверяет типы данных, если, конечно, вы не используете специальные пакеты вроде enforce. Так что добавление подсказок типов, казалось бы, не должно влиять на скорость работы.

Но это не совсем так. Дело в том, что эту функциональность обеспечивает модуль typing – один из самых медленных во всей библиотеке. Но PEP 560 ускоряет его в Python 3.7, добавив поддержку ядра. О деталях этого механизма можно не задумываться, просто наслаждайтесь скоростью.

Предварительное объявление

Система типов в Python весьма выразительна, но есть одна проблема: предварительное объявление. Все аннотации анализируются при импорте, поэтому имена должны быть указаны еще до использования. Вот такой код невозможен:

Здесь мы получим NameError, потому что нельзя ссылаться на класс Tree в методе __init__ этого же класса, ведь он еще не определен.

Проблему можно решить, заменив имя класса на строку:

Обсуждение этой проблемы можно посмотреть в PEP-484.

Python 4 официально разрешит предварительное объявление. Подобные рекомендации типов не будут обрабатываться до прямого запроса. А сейчас, в Python 3.7 у нас есть __future__:

И никаких фальшивых строчных параметров.

Отложенное выполнение аннотаций ускоряет работу программы. В mypy оно уже добавлено.

Обработка аннотаций

Рассмотрим бессмысленный пример, чтобы понять, как обрабатываются аннотации при импорте. Файл anno.py содержит следующий код:

Мы используем print как аннотацию для name, чтобы понять, когда именно она будет выполняться. Импортируем этот модуль:

Как видно, аннотация выполнилась при импорте. Функция print возвращает None, поэтому name объявлена с этим значением.
Импортируем __future__ :

Теперь print не вызывается:

«Now!» больше не выводится. Сама аннотация записывается в __annotations__. Вызовем ее вручную с помощью typing.get_type_hints() или eval():

Словарь __annotations__ никогда не обновляется, поэтому вам нужно выполнять аннотацию при каждом использовании.

Простой импорт с importlib.resourses

При сборке проекта всегда возникает проблема обработки путей к файлам данных. Есть три основных варианта:

    • Жестко задать пути;
    • Добавить файлы внутрь пакета и использовать __file__;
    • Обратиться к setuptools.pkg_resources.

У каждого из них есть недостатки. Первый абсолютно не портативный. Третий ужасно медленный. Также проект может оказаться внутри zip-архива и не иметь атрибута __file__.

В Python 3.7 появилось отличное решение – модуль importlib.resources. Например, ваши данные хранятся в отдельном пакете следующим образом:

Пакетная структура очень важна: в папке обязательно должен находиться файл __init__.py.

Обратиться к alice_in_wonderland.txt теперь можно следующим образом:

Аналогичный метод resources.open_binary() открывает бинарные файлы. Больше узнать о новом модуле можно из выступления Barry Warsaw на PyCon 2018.

Есть возможность использовать importlib.resourses в более старых версиях языка. А здесь вы найдете инструкцию по переходу с pkg_resourses.

Набор консольных команд

В Python 3.7 появилась новая опция -X. Документация сообщает, что она зарезервирована для различных вариантов реализации и приводит несколько примеров, определенных в CPython.

Например, с помощью команды -X importtime можно узнать, сколько времени занимает импортирование модулей.

В столбце cumulative отображается накопленное значение времени в микросекундах. Получается, что пакет plugins загружался около 0.03 секунды, причем дольше всего импортировался модуль importlib.resourses. В столбце self можно увидеть время без учета вложенных загрузок.

Еще одна полезная команда – -X dev. Она включает режим разработки с функциями отладки и runtime-проверки, которые не используются по умолчанию из-за своей медлительности. Также этот режим подключает модуль faulthandler.

Отметим еще одну команду -X utf8, которая включает режим UTF-8, игнорирующий текущую локаль операционной системы. Подробное описание в PEP-540.

Оптимизации Python 3.7

Подведем небольшой итог оптимизациям в новом стандарте языка. Можно сказать, что Python наконец разогнался.

Большинство методов стандартной библиотеки теперь вызываются быстрее примерно на 20%, а сам интерпретатор тратит на треть меньше времени для старта. Самый медлительный модуль typing ускорился в целых 7 раз! Кроме того, есть еще множество более мелких оптимизаций. В результате Python 3.7 стал действительно быстрым. На текущий момент это самая скоростная версия CPython.

Про все новшества вы можете прочитать в документации. Если хотите убедиться лично, воспользуйтесь новой командой -X importtime.

Перевод статьи Cool New Features in Python 3.7

Узнайте о Python больше:

Интересуетесь программированием на Python?

Подпишитесь на нашу рассылку, чтобы получать больше интересных материалов:

И не беспокойтесь, мы тоже не любим спам. Отписаться можно в любое время.




1 комментарий

Оставьте комментарий