Вышел Python 3.11

Вышел Python 3.11
Изображение с Python.org.
👋
Хочешь поучаствовать в жизни сайта? Мы ищем авторов!

Новый релиз включает скорость, новый вывод трейсбеков, группы исключений, tomllib, группы задач asyncio, плюс расширения в регулярные выражения и тайп хинты.

Сообщение о новом релизе опубликовали на Python.org.

Ниже я опишу нововведения подробнее.

Улучшение производительности

Скорость работы 3.11 увеличилась в среднем на 25%, а время старта уменьшилось на 10-60%.

Описание оптимизаций CPython доступно в документации.

Указка на ошибку в трейсбеке

Компилятор CPython поумнел и теперь умеет преобразовывать указатель на ошибку в байт-коде в ссылку на ошибку в исходниках. Трейсбеки читаются лучше.

Исключение в следующем коде:

def foo(x):
    1 + 1/0 + 2

def bar(x):
    1 + foo(x) + foo(x)

bar(bar(bar(2)))

Выводится так:

Traceback (most recent call last):
  File "test.py", line 7, in <module>
    bar(bar(bar(2)))
            ^^^^^^
  File "test.py", line 5, in bar
    1 + foo(x) + foo(x)
        ^^^^^^
  File "test.py", line 2, in foo
    1 + 1/0 + 2
        ~^~
ZeroDivisionError: division by zero

Группы исключений и обработка нескольких исключений одновременно

Новые классы ExceptionGroup и BaseExceptionGroup объединяют несколько исключений в одну древовидную структуру:

>>> eg = ExceptionGroup(
...     "one",
...     [
...         TypeError(1),
...         ExceptionGroup(
...             "two",
...              [TypeError(2), ValueError(3)]
...         ),
...         ExceptionGroup(
...              "three",
...               [OSError(4)]
...         )
...     ]
... )
>>> import traceback
>>> traceback.print_exception(eg)
  | ExceptionGroup: one (3 sub-exceptions)
  +-+---------------- 1 ----------------
    | TypeError: 1
    +---------------- 2 ----------------
    | ExceptionGroup: two (2 sub-exceptions)
    +-+---------------- 1 ----------------
      | TypeError: 2
      +---------------- 2 ----------------
      | ValueError: 3
      +------------------------------------
    +---------------- 3 ----------------
    | ExceptionGroup: three (1 sub-exception)
    +-+---------------- 1 ----------------
      | OSError: 4
      +------------------------------------

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

try:
    raise ExceptionGroup("test group", [ValueError("value error"), OSError("os error")])
except ValueError as e:
   # Не вызовется
   print(f"ValueError handled: {e}")
except OSError as e:
   # Не вызовется
   print(f"OSError handled: {e}")
  + Exception Group Traceback (most recent call last):
  |   File "test.py", line 2, in <module>
  |     raise ExceptionGroup("test group", [ValueError("value error"), OSError("os error")])
  | ExceptionGroup: test group (2 sub-exceptions)
  +-+---------------- 1 ----------------
    | ValueError: value error
    +---------------- 2 ----------------
    | OSError: os error
    +------------------------------------

Но с новым синтаксисом except* можно ловить группы по содержимому их дерева.

try:
    raise ExceptionGroup("test group", [ValueError("value error"), OSError("os error")])
except* ValueError as e:
   # Не вызовется
   print(f"ValueError handled: {e}")
except* OSError as e:
   # Не вызовется
   print(f"OSError handled: {e}")
ValueError handled: test group (1 sub-exception)
OSError handled: test group (1 sub-exception)

Что интересно, блоков except* может выполниться несколько, если они все подходят.

Парсер формата TOML включен в стандартную библиотеку

Модуль предоставляет публичные функции load и loads наподобие модуля json.

Пример файла TOML:

[database]
enabled = true
ports = [ 8000, 8001, 8002 ]
data = [ ["delta", "phi"], [3.14] ]
temp_targets = { cpu = 79.5, case = 72.0 }

Пример парсинга:

import tomllib

with open("t.toml", "rb") as f:
    print(tomllib.load(f))
{'database': {'enabled': True, 'ports': [8000, 8001, 8002], 'data': [['delta', 'phi'], [3.14]], 'temp_targets': {'cpu': 79.5, 'case': 72.0}}}

Группы асинхронных задач

asyncio.TaskGroup - замена asyncio.gather, которая позволяет планировать задачи, выполнять произвольный код между планированием, отменять задачи, если любая из них падает с ошибкой, и собирать ошибки из задач в одну группу исключений.

Новый API хорошо сочетается с ExceptionGroup:

try:
    async with asyncio.TaskGroup() as tg:
        tg.create_task(task1())
        await do_something_else()
        tg.create_task(task2())
 except* ValueError as e:
     logger.error("ValueError has occured")

Атомарные группы в регулярных выражениях

Новый релиз добавляет в re поддержку синтаксиса атомарных групп ((?>...)) и "possesive quantifiers" (подвариант атомарных групп).

Логика работы атомарных групп слишком сложна для этого поста. В официальном issue атомарных групп ссылаются на описание здесь.

Расширения в тайп хинты

  • Новый тип Self позволяет унаследованным методам ссылаться на унаследовавшие их подклассы.
  • TypeVarTuple позволяет писать генерики с переменных числом параметров.
  • LiteralString - новый тип для строки-литерала.
  • Возможность указывать в TypedDict, какие ключи обязательны, а какие могут отсутствовать с помощью Required и NotRequired.
  • "Data Class Transforms" - новый API, обобщающий типизацию dataclass'ов, чтобы внешние фреймворки вроде Pydantic, SQLAlchemy, Django и др. могли реализовать поддержку тайп-хинтов в своих динамически-генерируемых типах.

Материал подготовлен с ❤️ редакцией Кухни IT.

Олег Ямников

Олег Ямников

Главный кухонный корреспондент.