Новости
Вышел Python 3.11. Что нового?

Вышел Python 3.11. Что нового?

В конце октября вышла в свет новая версия языка программирования Python и интерпретатора CPython. Рассмотрим новшества, которые несёт с собой Python 3.11.

Стала доступна для скачивания или установки из репозиториев новая версия Python 3.11.0.

Изображение Python 3.11. Что нового?

Python 3.11 стал быстрее!

В данном релизе разработчики сконцентрировались на увеличении скорости работы интерпретатора CPython — основного средства запуска кода на языке программирования Python. В зависимости от тестов производительность версии 3.11 выше предыдущей 3.10 на 10-60 процентов. Условно можно сказать, что в среднем новая версия будет в 1.25 раз быстрее.

Ранее уже сообщалось, что бета-версия 3.11 обыгрывает 3.10.4 (актуальную стабильную на тот момент версию) в среднем на 25%. Также указывалось, что есть планы продолжать "ускорять" Python.

Новые возможности: группы исключений (ExceptionGroups)

В Python 3.11 добавляется новый стандартный тип исключений — ExceptionGroups, а также синтаксическая конструкция except* — для обработки этих самых групп исключений.

Общая идея в следующем: сейчас в интерпретаторе можно пробросить только одно исключение за раз. Есть вариант привязывать одно исключение к другому через raise ... from ..., но тогда они скорее "причина" основного исключения.

Есть ситуации, где нужно пробросить множество несвязанных исключений. К примеру, при конкурентной работе происходят ошибки в разных корутинах — какую из них возвращать? Они не знают, что они как-то связаны, но, скорее всего, именно во взаимодействии и появилась "исключительная ситуация".

Так у нас появляется потребность в групповых исключениях: BaseExceptionGroup(BaseException) и ExceptionGroup(BaseExceptionGroup, Exception) — по аналогии с иерархией обычных исключений. Оба они имеют два позиционных параметра: сообщение, список исключений.

>>> 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
      +------------------------------------

Из них можно получать подгруппы, разделять по условию и т.д. Подробнее в PEP-0654.

except* работает как "распаковка" группы исключений для сопоставления: какой обработчик применить. Например:

>>> try:
...     raise ExceptionGroup("problem", [BlockingIOError()])
... except* OSError as e:   # Как и с except - родительское исключени также матчится
...     print(repr(e))
... except* BlockingIOError: # Если бы не except выше, вызвался бы этот обработчик
...     print('never')
...
ExceptionGroup('problem', [BlockingIOError()])

Улучшенное отображение ошибок

В Python 3.11 постарались улучшить систему подсказок о том, в каком месте произошла ошибка. Думаю, все программисты на Python это скоро заметят и порадуются. Самим же "пользователям" CPython ничего для этого делать особо не надо, поэтому ограничимся примером. Есть у нас код:

x['a']['b']['c']['d'] = 1

При запуске раньше мы могли получить ошибку:

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    x['a']['b']['c']['d'] = 1
TypeError: 'NoneType' object is not subscriptable

После чего ругались: какой же элемент из этой цепочки был None? С Python 3.11 вывод будет следующим:

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    x['a']['b']['c']['d'] = 1
    ~~~~~~~~~~~^^^^^
TypeError: 'NoneType' object is not subscriptable

— по стрелочкам теперь ясно, что None лежал в x['a']['b']['c'].

Новый тип данных — Self

Думаю, всем, кто пользуется type-hinting-ом, уже давно надоело для порождающих и прочих методов, что используют объекты этого же класса в аргументах, указывать тип значения в кавычках. Поясню на примере:

class Shape:
    def set_scale(self, scale: float) -> 'Shape':
        self.scale = scale
        return self

— так IDE понимает, что возвращается тип Shape, но написать без кавычек мы не можем, ведь на этот момент класс Shape ещё не определён... Вот и появился "костыль" с кавычками.

Теперь можно указать тип возвращаемого значения без кавычек:

from typing import Self

class Shape:
    def set_scale(self, scale: float) -> Self:
        self.scale = scale
        return self

И ещё несколько новшеств

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

В частности, модуль typing:

  • TypeVarTuple позволяет описывать "вариативные дженерики", с ними можно описывать сразу несколько типов.
  • Появился тип LiteralString, который соответствует строковой константе.
  • Required / NotRequired для обозначения обязательных / не обязательных полей TypedDict.

Добавлен класс asyncio.TaskGroup, позволяющий, используя синтаксис асинхронного контекстного менеджера, дождаться исполнения запущенных в нём корутин:

async def main():
    async with asyncio.TaskGroup() as tg:
        task1 = tg.create_task(some_coro(...))
        task2 = tg.create_task(another_coro(...))
    print("На этот момент оба таска завершены.")

Для регулярок подвезли ревнивую (сверхжадную) квантификацию и атомарную группировку.

Для любителей dataclass-ов (а всё, кажется, к ним и идёт, если нужно описывать модели) добавили dataclass_transform, позволяющий лучше настроить поведение dataclass-ов. Авторы PEP-0681 явно вдохновлялись проектами attrs, pydantic, да и в целом ORM-ками.

Добавлен новый модуль tomllib для работы с форматом TOML подобно тому, как сделали с json, yaml.

Думаю, это основные изменения, которые пришли к нам с выходом Python 3.11. За более подробным списком можно сходить на официальную страницу со списком изменений.