Резюме
-
Появление долгожданных фич из нового REPL в PDB
-
Большое количество фиксов для shutil, так что можно наконец‑то перестать молиться при его использовании
-
Несколько небольших улучшений многопоточности
-
Новый синтаксис аннотаций позволяет использовать списки и лямбда‑функции.
-
Python 3.13 все еще не стал значительно быстрее. Увы.
По традиции
Python 3.13 — отличный релиз, полный различных фич и улучшений, но уже есть тонна статей, которые подробно разбирают release notes. Если вам нужна хорошая выжимка — у RealPython есть хорошая статья, но я не вижу смысла проходиться по ним еще раз в этой статье.
Так что мы не будем говорить про новый REPL, no‑GIL сборку, экспериментальный JIT‑компилятор, устаревшие штуки, новые плюшки системы типов или улучшенные сообщения об ошибках (как всегда, мое любимое).
Вместо этого я прочитал коротенькую книжку, которую они называют ченджлогом и мы посмотрим на то, о чем многие не говорили, но заинтересовало лично меня.
Make debugging great again
Несмотря на спартанскую эргономику, я очень люблю pdb и даже написал неплохое введение в него.
Но если вы когда‑либо пробовали сделать так:
try:
1 / 0
except ZeroDivisionError as e:
breakpoint()
Вы прекрасно знаете, что произойдет при попытке чтения e
:
-> breakpoint()
(Pdb) e
*** NameError: name 'e' is not defined
Это выбешивает, в особенности потому, что с этим обычно сталкиваешься и так в не самом лучшем расположении духа.
И это наконец‑то исправили.
Слава Апофису, Кетцалькоатлю и Йормунганду!
Но это не все. Сам pdb также получил некие улучшения:
-
Наконец, многострочное редактирование!
-
Автозаполнение кода, как в новом REPL.
-
break допускает использование dotted path, позволяя с легкостью динамически добавлять брейкпоинты в библиотеки.
-
Поддержка pdb в zipapps. Почему бы и нет.
-
Исправили проглатывание аргументов в pdb CLI. Это должно понравиться команде VSCode.
-
pdbrc наконец‑то не сломан. Никогда не использовали? Вот и я нет. Потому что он не работал.
-
Режим post‑mortem работает даже для SyntaxError. Это то, что нужно при работе с exec().
Все это само по себе уже хорошая причина использовать 3.13.
Немного любви для работы с файловой системой
Казалось бы, 30-летний язык не должен иметь проблем при работе с путями и файлами, но, оказывается, всегда есть куда расти.
Модуль shutil, предоставляющий высокоуровневые операции с ФС, такие как рекурсивное удаление или копирование претерпел следующие изменения: много—много багов было исправлено (в особенности относящиеся к обработке ошибок при рекурсии), а также добавлены некоторые опции (например, теперь есть возможность выбрать, как обрабатывать симлинки).
Я всегда опасался использовать shutil из‑за таких «особенностей». Рекурсивное погружение ломалось тем или иным образом. Благодаря этим фиксам я смогу снова попробовать использовать ее, поскольку она действительно удобна, когда работает.
Аналогично и для zipfile.Path — pathlib‑совместимой обертки для обхода zip‑файлов, о существовании которой вы скорее всего не знали с момента ее появления в версии 3.8. И не знали вы о ней по очень простой причине — в ней не было ничего хорошего. Но версия 3.13 подарила нам много QoL‑патчей для нее, значительно улучшая обработку директорий, которая раньше требовало очень много телодвижений. Так что теперь ее можно будет использовать чаще.
И, наконец, сама pathlib получила много небольших оптимизаций, касающихся производительности, теперь многие операции под капотом используют строки вместо объектов Path
. Сериализация также должна стать быстрее. Мне нравится pathlib, но возможность боттлнека — известная проблема, так что это отличные новости. Сам я производительность на практике не замерял, так что пока воздержусь от комментариев.
Но дело не только в производительности - также было несколько приятных изменений в API. К примеру, Path.glob()
и rglob()
теперь принимают в качестве паттернов path-like объекты, Path.glob()
теперь возвращает и файлы, и директории, если в конец паттерна добавить **
, а также был добавлен метод Path.from_uri()
.
Небольшие победы в параллелизме
-
asyncio.as_completed()
теперь возвращает объект, который одновременно и асинхронный, и обычный итератор. Да, не что‑то масштабное, но уже неплохо. -
asyncio.TaskGroup
— отличная вещь, если еще не используете — настоятельно рекомендую. И теперь при вызовеcreate_task()
для неактивной группы корутина будет закрыта, предотвращаяRuntimeWarning
. -
queue.Queue
теперь может быть явно закрыта вызовомshutdown()
, помогая сообщить остальной системе, что пришло время перестать передавать ей данные. -
Максимальное количество воркеров в пуле было увеличено выше 62, просто на случай, если вы — тот самый человек из треда на Reddit.
Изменения аннотаций, о которых никто не просил
Помните, что аннотации изначально не были ограничены только типами? Это был эксперимент, чтобы проверить, для чего их будут использовать люди и потому они принимают произвольные выражения Python.
Но, похоже, недостаточно произвольные, поскольку была добавлена возможность использования списков и лямбда‑функций.
Тикет с баг‑репортом достаточно забавный:
The following code causes a
SystemError
during compilation.
class name_2[*name_5, name_3: int]:
(name_3 := name_4)
class name_4[name_5: name_5]((name_4 for name_5 in name_0 if name_3), name_2 if name_3 else name_0):
pass
Да что ты говоришь, Фрэнк, да что ты говоришь…
Что ж, посмотрим, какие еще способы поиздеваться над этим функционалом придумают разработчики.
Разочарования
Улучшения производительности не такие высокие, как ожидалось в этом релизе — как и в 3.12 мы получили лишь небольшое улучшение в традиционных бенчмарках для асинхронных операций (надеюсь, это не навредит моему оптимизму относительно pathlilb). Ускорение Python гораздо, гораздо сложнее, чем Guido предполагал. Это можно было понять из прошлых неудавшихся попыток, так что не особо удивительно.
Как я упоминал в прошлом посте, инкрементальные изменения GC и правда были отменены, поскольку не дали ожидаемых результатов.
Более того, несмотря на большое количество улучшений import в ченджлоге, мои замеры не показали значительной разницы во времени запуска Python на моей машине.
Ну и поверх всего этого неизбежные поломки:
Если вы внимательно прочитаете изменения Path.glob()
выше, вы можете заметить, что возвращаемое значение может значительно меняться и я предполагаю, что это сломает чей‑то код.
Ну и несколько других деталей, которые могут не понравиться некоторым:
-
Поддержка использования
pathlib
. Объектыpath
больше не могут использоваться для управления контекстом. Не то чтобы это было полезно. И вообще это путало. Так что, наверное, хорошее изменение, но вы знаете. -
Запуск новых тредов и процессов с помощью
os.fork()
во время выключения интерпретатора (например, из хэндлеровatexit
) больше не поддерживается. -
Позиционные аргументы
maxsplit
,count
иflags
дляre.split()
,re.sub()
иre.subn()
помечены как устаревшие в пользу использования ключевых слов. Пока не убрано, просто предупреждение. Мне кажется несколько излишним. -
Аналогично для всех аргументов
sqlite3.connect()
, кроме первого. -
Устаревание незадокументированных функций
glob.glob0()
иglob.glob1()
. Я терпеть их не мог, так как они путали моих студентов, так что я рад этому изменению, но все же. -
Файлы
.pth
с названиями, начинающимися с точки или имеющие атрибутhidden
теперь игнорируются по причинам безопасности. -
Хэдеры C API были почищены, так что ждем драконов с той стороны.
Разработка языка это всегда тонкий баланс между чистотой кода, современными фичами и отсутствием технического долга с одной стороны и поддержанием достаточной стабильности для сохранения продуктивности сообщества с другой.
Всегда найдутся как люди, которые будут говорить, что язык затрял в прошлом, так и те, кто будут спрашивать, почему опять все сломали. Здесь не победить. Разработка языка — работа неблагодарная.
Разные приятные изменения
Давайте закончим на позитивной ноте. У меня нет какой-либо категории для этих изменений:
-
python -m venv
добавляет файл .gitignore, который автоматически игнорирует виртуальное окружение. -
json.dumps()
сindent
теперь будет использовать C-энкодер JSON, что сделает его значительно быстрее. Ну и ошибки парсинга из-за завершающей запятой теперь гораздо понятнее. -
atexit()
лучше работает с multiprocessing. Скорее всего связано с упомянутым ранее устареванием :) -
Dataclass теперь вызывает exec() один раз на 1 dataclass вместо вызова для каждого добавляемого метода. Это может сделать процесс их создания до 20% быстрее.
-
Была добавлена объединенная операция сложения-умножения
math.fma(x, y, z)
. На случай, если вам необходимо работать с полиномами прямо здесь, прямо сейчас. -
time.sleep()
теперь создает auditing event. Кто-то получит письмо на эту тему и я не могу дождаться. Нет, серьезно, вы же не хотите получитьsleep(1e9)
в критических проектах. -
Такие функции
re
какre.findall()
,re.split()
,re.search()
иre.sub()
, выполняющие короткие повторяющиеся сравнения теперь могут быть прерваны пользователем. Меньше причин, по которым Ctrl + C может не срабатывать. -
Параметр количества в
str.replace()
теперь может быть ключевым словом.
Что удивило меня больше всего при прочтении ченджлога версий 3.12 и 3.13 - фокус на улучшении уже существующего функционала. Большое количество багфиксов, небольших правок API, попытки немного улучшить производительность, чистка кода и удаление устаревшего. Даже известная плохая документация argparse
стала лучше.
Спустя годы преследования новых фич с асинхронностью, типизацией, моржовым оператором и несколькими интерпретаторами, я ожидал от новых людей в команде разработки лишь увеличения этой тенденции. Но вместо этого мы получили улучшения сообщений об ошибках, улучшенный PDB, более гибкий парсер, улучшенный REPL и так далее.
И для меня это знак, что им не все равно.
Автор: MrPizzly