Python 3.15: UTF-8 по умолчанию, lazy import, frozendict и профилировщик Tachyon

9 октября 2026 года релиз-менеджер Хьюго ван Кеменаде объявил в блоге Python Insider о выходе Python 3.15. По его словам, в релиз вошли 5643 коммита от 1012 участников. OpenNet 10 октября разобрал главные изменения, а ниже то, что важно разработчикам, по документации «Что нового в Python 3.15».

Главное в Python 3.15

В обзоре новинок к самым заметным изменениям отнесены ленивые импорты, встроенные типы frozendict и sentinel, UTF-8 как кодировка по умолчанию, распаковка в списковых включениях и стабильный ABI для сборок без GIL. В стандартной библиотеке появился пакет profiling с профилировщиком Tachyon, а в интерпретаторе заметно обновили экспериментальный JIT.

UTF-8 по умолчанию

Теперь Python использует UTF-8 как кодировку по умолчанию независимо от настроек системы. Это относится к операциям ввода и вывода без явного encoding: например, open(‘flying-circus.txt’) откроет файл как UTF-8. Это решение из PEP 686.

Документация советует всегда передавать encoding явно, так код одинаково работает в разных версиях Python. Найти места, на которые изменение может повлиять, помогает опциональное предупреждение о кодировке (encoding warning). Значение encoding=’locale’, которое берёт кодировку текущей локали, поддерживается с Python 3.10. Чтобы вернуть прежнее поведение, UTF-8 режим выключают переменной PYTHONUTF8=0 или опцией -X utf8=0.

Ленивые импорты: ключевое слово lazy

Большие приложения часто долго стартуют из-за импортов. В Python 3.15 по PEP 810 появилось мягкое ключевое слово lazy: модуль не загружается, пока имя из него не понадобилось в первый раз. Работает и для import, и для from … import.

lazy import json
lazy from pathlib import Path

print("Starting up...")  # json и pathlib ещё не загружены

data = json.loads('{"key": "value"}')  # json загружается здесь
p = Path(".")  # pathlib загружается здесь

Если модуль не удалось загрузить, например его нет, ошибка возникнет в момент первого обращения, а не при импорте. В трассировке будут и место обращения, и исходная строка импорта. Под капотом на месте модуля стоит лёгкий прокси types.LazyImportType.

Есть и глобальное управление. Опция -X lazy_imports и переменная PYTHON_LAZY_IMPORTS принимают all (все импорты ленивые) и normal (по умолчанию, учитывается слово lazy в коде). Менять и узнавать режим на ходу можно через sys.set_lazy_imports() и sys.get_lazy_imports(), а sys.set_lazy_imports_filter() позволяет, например, сделать ленивыми только модули своего приложения.

Ограничения: lazy допустим только на уровне модуля. Внутри функции, класса или блока try, except, finally будет SyntaxError, как и для lazy from module import * и lazy from __future__. Если нужно поддерживать версии старше 3.15, модуль может объявить список __lazy_modules__, и обычные import этих модулей станут ленивыми на 3.15 и новее.

frozendict и sentinel

Во встроенные типы добавлен frozendict, неизменяемый словарь (PEP 814). Он не подкласс dict, а наследуется прямо от object. Порядок вставки сохраняется, но при сравнении не учитывается, а хэшироваться frozendict можно, если хэшируемы все ключи и значения. Изменить его нельзя: a[‘z’] = 3 даёт TypeError.

Модули copy, decimal, json, marshal, plistlib (только сериализация), pickle, pprint и xml.etree.ElementTree научили принимать frozendict. Если у вас есть проверки isinstance(arg, dict), документация предлагает заменить их на isinstance(arg, (dict, frozendict)) или на collections.abc.Mapping, который подходит и для MappingProxyType. Для JSON есть удобный приём: в json.load() и json.loads() появился параметр array_hook, и связка frozendict в object_pairs_hook с tuple в array_hook даёт полностью неизменяемую структуру.

Тип sentinel (PEP 661) нужен для уникальных значений-меток с понятным представлением. Такие объекты сохраняют идентичность при копировании, работают в аннотациях с оператором | и сериализуются через pickle, если их можно импортировать по модулю и имени.

Профилирование: Tachyon и frame pointers

Появился пакет profiling (PEP 799), который собирает встроенные профилировщики в одном месте: profiling.tracing с детерминированной трассировкой вызовов и новый profiling.sampling. Модуль cProfile остаётся псевдонимом для совместимости, а модуль profile объявлен устаревшим и будет удалён в Python 3.17, вместо него предлагается profiling.tracing.

Statistical sampling profiler получил имя Tachyon. Он периодически снимает стек вызовов работающего процесса, поэтому накладные расходы почти нулевые, а частота выборки доходит до 1 000 000 Гц. Подключаться можно к уже запущенному процессу по PID без перезапуска, запускать скрипты или модули под профилированием и снимать моментальный дамп стеков всех потоков. Режимы измерения: реальное время, процессорное время, время с удержанием GIL и время обработки исключений. Результаты выводятся в форматах pstats, collapsed stacks, интерактивного flame graph, Gecko для Firefox Profiler и heatmap, есть и живой режим в терминале.

Ещё одно изменение для тех, кто разбирается с производительностью и падениями: CPython по умолчанию собирается с флагами -fno-omit-frame-pointer и -mno-omit-leaf-frame-pointer (PEP 831). Так системные профилировщики, отладчики и инструменты на eBPF быстрее и надёжнее разворачивают стек. Флаги попадают в sysconfig, поэтому расширения, собранные с его настройками, их унаследуют. Если вы собираете C, C++ или Rust код отдельно, документация просит включить эквивалентные флаги самостоятельно: один компонент без frame pointer может сломать раскрутку стека во всём процессе.

Экспериментальный JIT

В бенчмарках pyperformance JIT даёт в среднем 7-8% ускорения по сравнению со стандартным интерпретатором на x86-64 Linux и 11-12% на AArch64 macOS. Относительно сборки без JIT разброс по тестам от замедления примерно на 15% до ускорения более чем вдвое. Главные изменения: переработанный фронтенд трассировки, который записывает реальные пути выполнения, простая аллокация регистров, больше оптимизаций и поддержка раскрутки стека для GDB и GNU backtrace().

LLVM 21 нужен только при сборке CPython с JIT, пользователям он не требуется.

Что ещё появилось

  • Распаковка в включениях (PEP 798): [*L for L in lists] склеивает вложенные списки, а {**d for d in dicts} сливает словари.
  • Файлы .start (PEP 829): в них записывают точки входа вида pkg.mod:callable, которые Python вызывает при старте. Строки import в .pth-файлах объявлены устаревшими, а при наличии подходящего .start-файла игнорируются. Строки, которые расширяют sys.path, остаются как были.
  • Сообщения об ошибках: при AttributeError Python подсказывает вложенный атрибут, например «Did you mean ‘.inner.area’», а для методов из JavaScript, Java, Ruby и C# предлагает аналог из Python, скажем .append вместо .push.
  • Типизация: у TypedDict появились параметры closed и extra_items, а в typing добавлен TypeForm для значений, которые сами являются выражениями типов.
  • C API: PyBytesWriter для сборки bytes, защита от завершения интерпретатора (PEP 788) и стабильный ABI для сборок без GIL (PEP 803).
  • Официальные сборки: для 64-битной Windows используется интерпретатор с хвостовыми вызовами, а на macOS по умолчанию ставится поддержка free-threading.

Познакомиться с новинками можно в игре whatsnewt: это текстовое приключение из 18 головоломок, где каждая завязана на реальную возможность Python 3.15 и требует написать и запустить код. Запуск: uvx –python 3.15 whatsnewt. Подробнее об игре написано на странице релиза.

Сколько будет жить ветка 3.15

Согласно PEP 790, исправления ошибок для Python 3.15 выходят примерно раз в два месяца в течение двух лет, последнее такое обновление выпустят примерно к релизу 3.17.0. После этого, как ожидается, ещё три года будут выходить обновления безопасности только в исходниках, то есть до пяти лет с момента релиза, примерно до октября 2031 года.

Что проверить разработчику

  • Найдите вызовы open() и другие операции с текстом без явного encoding. На UTF-8 по умолчанию они теперь читают и пишут файлы иначе, чем раньше в окружениях с другой локалью. Явный encoding работает одинаково во всех версиях.
  • Если код проверяет isinstance(x, dict), решите, нужно ли принимать frozendict.
  • Используете profile, переходите на profiling.tracing до выхода Python 3.17.
  • В пакетах с .pth-файлами, где есть строки import, посмотрите на новые .start-файлы.
  • Если приложение долго стартует из-за импортов, попробуйте lazy import на уровне модуля или режим -X lazy_imports.

В итоге Python 3.15 вышел 9 октября 2026 года и приносит UTF-8 по умолчанию, ленивые импорты, frozendict и sentinel, профилировщик Tachyon и более быстрый экспериментальный JIT. Перед обновлением самое важное проверить работу с текстовыми файлами без явной кодировки.

Источники и ссылки


Комментарии загружаются…