Команда Python for Devs подготовила перевод статьи о Python 3.14. В новой версии язык стал не просто «ругаться» на ошибки, а объяснять их понятным человеческим языком — с подсказками, где проблема и как её исправить. Теперь вместо сухого invalid syntax вы получите конкретное объяснение и даже намёк на решение.


Когда в Python 3.9 появился новый парсер PEG (Parsing Expression Grammar), это открыло дорогу к более понятным сообщениям об ошибках в Python 3.10. В Python 3.11 они стали ещё лучше, и эта работа продолжилась в Python 3.12.

В Python 3.13 сообщения доработали: улучшили форматирование, сделали пояснения понятнее, добавили контекст к сложным случаям и сделали многострочные ошибки более читаемыми. Эти улучшения базировались на PEP 657, который ввёл точную привязку ошибок к строкам кода в трассировках начиная с Python 3.11.

Теперь Python 3.14 делает следующий шаг вперёд — вместе с другими крупными изменениями, такими как PEP 779 (официальная поддержка сборки с free-threading) и PEP 765 (запрет выхода из блока finally через return, break или continue). Особенность улучшений именно в 3.14 — фокус на типичных ошибках.

Каждое новое сообщение об ошибке следует единому шаблону:

  1. Указывает, в чём именно ошибка.

  2. Объясняет проблему простыми словами.

  3. Когда возможно — предлагает вариант исправления.

В Python 3.14 улучшены сообщения об ошибках для SyntaxErrorValueError и TypeError. Вот десять изменений, которые рассматриваются в этом материале:

Скрытый текст
  1. Опечатки в ключевых словах — SyntaxError
    Раньше: invalid syntax
    Теперь: invalid syntax. Did you mean 'for'?

  2. elif после else — SyntaxError
    Раньше: invalid syntax
    Теперь: 'elif' block follows an 'else' block

  3. Условные выражения — SyntaxError
    Раньше: invalid syntax
    Теперь: expected expression after 'else', but statement is given

  4. Незакрытая строка — SyntaxError
    Раньше: invalid syntax
    Теперь: invalid syntax. Is this intended to be part of the string?

  5. Префиксы строк — SyntaxError
    Раньше: invalid syntax
    Теперь: 'b' and 'f' prefixes are incompatible

  6. Ошибки распаковки — ValueError
    Раньше: too many values to unpack (expected 2)
    Теперь: too many values to unpack (expected 2, got 3)

  7. Список как цель в import — SyntaxError
    Раньше: invalid syntax
    Теперь: cannot use list as import target

  8. Нехешируемые типы — TypeError
    Раньше: unhashable type: 'list'
    Теперь: cannot use 'list' as a dict key (unhashable type: 'list')

  9. Ошибки области определения math — ValueError
    Раньше: math domain error
    Теперь: expected a nonnegative input, got -1.0

  10. Ошибки async with — TypeError
    Раньше: 'TaskGroup' object does not support the context manager protocol
    Теперь: object does not support the context manager protocol...Did you mean to use 'async with'?

В примерах этой статьи показаны и старые сообщения из Python 3.13, и новые — из Python 3.14, чтобы вы могли увидеть разницу даже без установки свежей версии.

1. Более понятные подсказки при опечатках в ключевых словах

Опечатка — это обычно крошечная ошибка, иногда всего одна лишняя буква, но её достаточно, чтобы полностью сломать код. Среди самых частых ошибок синтаксиса в Python — опечатки в ключевых словах.

В Python 3.13 и более ранних версиях опечатка в ключевом слове приводила к стандартной ошибке синтаксиса, которая никак не подсказывала, в чём дело:

>>> forr i in range(5):
  File "<python-input-0>", line 1
    forr i in range(5):
         ^
SyntaxError: invalid syntax

Сообщение указывает на проблемное место с помощью символа каретки (^), и это хотя бы помогает понять, где Python заметил ошибку. Но никакой подсказки о том, что именно не так, нет. Фраза “invalid syntax” формально правильная, но на практике мало полезная.

Разобраться, что forr должно быть for, приходится самому. Иногда это очевидно, но, если сосредоточен на логике, а не на орфографии, поиск одной неправильной буквы может занять неожиданно много времени.

В Python 3.14 компилятор распознаёт, когда вы набрали что-то похожее на ключевое слово, и сразу предлагает исправление:

>>> forr i in range(5):
  File "<python-input-0>", line 1
    forr i in range(5):
    ^^^^
SyntaxError: invalid syntax. Did you mean 'for'?

Новые сообщения в Python 3.14 используют расстояние Левенштейна, или редакционное расстояние, чтобы определить, когда неизвестное слово почти совпадает с ключевым словом Python. Но всё равно стоит быть внимательным и проверять подсказку:

>>> why True:
  File "<python-input-0>", line 1
    why True:
    ^^^
SyntaxError: invalid syntax. Did you mean 'with'?

Иногда Python может предложить не то ключевое слово, если другое ближе по написанию к вашей опечатке. Кроме того, подсказки появляются только тогда, когда ошибка находится в пределах «разумного» расстояния редактирования. В остальных случаях вы по-прежнему получите обычный SyntaxError с сообщением “invalid syntax”без всяких подсказок.

И это ещё не всё: Python 3.14 помогает не только с простыми опечатками, но и в случаях, когда ключевые слова оказались не на своём месте в управляющих конструкциях.

2. Более строгие ошибки при использовании elif после else

Ключевые слова ifelif и else — это основа условных операторов в Python. Их нужно располагать в правильном порядке: сначала идёт условие if, затем любое количество блоков elif для дополнительных проверок, и в конце — необязательный else, который срабатывает во всех остальных случаях. Но довольно легко случайно поставить elifпосле else.

Если нарушить этот порядок, Python выдаст SyntaxError. В Python 3.13 вы получите общее сообщение “invalid syntax”, которое лишь указывает на ключевое слово elif:

>>> if x > 0:
...     print("positive")
... else:
...     print("not positive")
... elif x == 0:  # Это неверно
...     print("zero")
...
  File "<python-input-0>", line 5
    elif x == 0:  # Это неверно
    ^^^^
SyntaxError: invalid syntax

Каретка показывает, где именно Python заметил ошибку, но объяснения, почему такая конструкция некорректна, нет. Можно даже подумать, что elif в принципе нельзя использовать, или попытаться заменить его на else if или if else.

В Python 3.14 сообщение становится куда понятнее и сразу указывает на структурную проблему:

>>> if x > 0:
...    print("positive")
... else:
...    print("not positive")
... elif x == 0:
...    print("zero")
...
  File "<python-input-0>", line 5
    elif x == 0:
    ^^^^
SyntaxError: 'elif' block follows an 'else' block

Новое сообщение чётко говорит, в чём дело: elif не может стоять после else. Это помогает понять, что else должен быть последним блоком в условной цепочке — универсальным «заглушателем» для случаев, которые не подошли под условия if или elif.

И это не всё: в Python 3.14 улучшения коснулись не только условных операторов, но и условных выражений — «однострочного родственника» конструкции if / elif / else.

3. Более явные ошибки в условных выражениях

Помимо условных операторов, в Python есть ещё один механизм для ветвления логики — условные выражения, или тернарные операторы:

variable = expression_1 if condition else expression_2

Условные выражения позволяют компактно выбирать одно из двух значений в зависимости от условия. Как видно из названия, они должны содержать выражения, а не инструкции (statements). Чтобы понять, почему это важно, стоит вспомнить разницу между выражениями и инструкциями в Python.

В чём разница между выражением и инструкцией?

Эта разница в Python принципиальна, но не всегда очевидна. Выражения вычисляются и возвращают значения:

>>> 2 + 2
4

>>> len("Python")
6

>>> "hello".upper()
'HELLO'

Инструкции (statements) — это полноценные команды, которые говорят интерпретатору выполнить какое-то действие:

>>> # Assignment statement
>>> x = 10

>>> # Expression statement
>>> x + 5
15

>>> # Conditional statement
>>> if x > 5:
...     print("x is greater than 5")
...
x is greater than 5

>>> # Loop statement
>>> for i in range(3):
...     print(i)
...
0
1
2

В этом примере есть разные инструкции: присваивание (x = 10), инструкция-выражение (x + 5), условная (if) и цикл (for).

Ключевая разница:

  • Выражения возвращают значения.

  • Инструкции выполняют действия или управляют потоком выполнения.

В Python ключевое слово pass — это заглушка, инструкция, которая позволяет писать синтаксически корректный код, не выполняя при этом никаких действий.

Посмотрим, что произойдёт в Python 3.13, если случайно использовать pass внутри условного выражения:

>>> 1 if True else pass
  File "<python-input-0>", line 1
    1 if True else pass
                   ^^^^
SyntaxError: invalid syntax

В Python 3.13 это приводит к стандартной ошибке “invalid syntax”, которая никак не объясняет, почему здесь нельзя использовать pass. Сообщение не даёт подсказки о разнице между выражением и инструкцией.

В Python 3.14 сообщение стало более ясным и обучающим:

>>> 1 if True else pass
  File "<python-input-0>", line 1
    1 if True else pass
                   ^^^^
SyntaxError: expected expression after 'else', but statement is given

Теперь Python прямо говорит, что после else в условном выражении ожидается выражение, а не инструкция вроде pass.

Если нужно получить “ничего” в качестве значения в условном выражении, следует использовать None, а не pass. Кто знает — возможно, в Python 3.15 это даже станет частью новой подсказки.

А вот со строками возникают уже другие сложности, особенно когда речь заходит о правильном согласовании кавычек.

4. Подсказки при ошибках со строками

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

>>> message = "She said "Hello" to everyone"
  File "<python-input-0>", line 1
    message = "She said "Hello" to everyone"
                         ^^^^^
SyntaxError: invalid syntax

В Python 3.13 видно, что каретки указывают на слово Hello как на проблемное место. Но сообщение “invalid syntax” никак не объясняет, что именно произошло.

Python интерпретирует вторую кавычку перед словом Hello как закрывающую строку, начатую первой кавычкой. В Python 3.14 этот распространённый паттерн распознаётся, и сообщение становится куда полезнее:

>>> message = "She said "Hello" to everyone"
  File "<python-input-0>", line 1
    message = "She said "Hello" to everyone"
                         ^^^^^
SyntaxError: invalid syntax. Is this intended to be part of the string?

В Python 3.14 при некорректно закрытой строке возникает SyntaxError с сообщением: “Is this intended to be part of the string?” Такое уточнение меняет восприятие ошибки: вместо абстрактного «синтаксис неверен» вы понимаете, что проблема именно в формате строки и в том, как вы используете кавычки внутри кавычек.

Чтобы узнать, как правильно исправить подобные ошибки, откройте дополнительный блок с примерами решений.

Как исправить ошибку: SyntaxError: invalid syntax. Is this intended to be part of the string?

Чтобы устранить такую ошибку, можно воспользоваться разными подходами:

  • Использовать разные типы кавычек внутри строки:

>>> message = "She said 'Hello' to everyone"
>>> message = 'She said "Hello" to everyone'
  • Использовать одинаковые кавычки, но экранировать внутренние с помощью обратного слэша:

>>> message = "She said \"Hello\" to everyone"
>>> message = 'She said \'Hello\' to everyone'

У каждого способа есть своё применение:

  • Разные кавычки — самый простой и наглядный вариант для базовых случаев.

  • Экранирование — удобно, когда вы хотите придерживаться одного типа кавычек во всём коде

В Python 3.14 улучшенные сообщения об ошибках со строками помогают не только при написании диалогов, но и в других сценариях:

  • при работе с JSON, где нужны вложенные кавычки,

  • в SQL-запросах со строковыми литералами,

  • в регулярных выражениях, где кавычки должны совпадать,

  • в HTML-фрагментах с атрибутами в кавычках.

5. Более понятные ошибки при комбинации префиксов строк

Система префиксов строк в Python мощная, но иногда сбивает с толку, особенно когда нужно понять, какие сочетания допустимы. Каждый регистрозависимый префикс отвечает за свою задачу:

Префикс

Назначение

r

«Сырые» строки, в которых обратные слэши не интерпретируются как спецсимволы

b

Создание объекта bytes для бинарных данных

f

Включает f-строки с подстановкой выражений

u

Явно помечает Unicode-строки

t

Новый в 3.14 — шаблонные строки (t-strings)

Проблема возникает, когда пытаются объединить префиксы, которые кажутся совместимыми, но на самом деле не могут работать вместе. Например, значение не может быть одновременно текстом и бинарными данными: это разные типы с разными внутренними представлениями.

Префикс r можно сочетать с bf и t, так как он лишь контролирует обработку escape-последовательностей и не задаёт тип литерала. Но r нельзя использовать с u (Unicode-строки). Остальные префиксы — bftu — несовместимы друг с другом, так как определяют взаимоисключающие типы литералов.

В Python 3.13 при попытке объединить несовместимые префиксы получалось такое:

>>> text = fb"Binary {text}"
  File "<python-input-0>", line 1
    text = fb"Binary {text}"
             ^^^^^^^^^^^^^^^
SyntaxError: invalid syntax

Сообщение указывало на строку, но было стандартным “invalid syntax”, никак не поясняя, что не так именно с комбинацией префиксов.

В Python 3.14 ошибка стала конкретной:

>>> text = fb"Binary {text}"
  File "<python-input-0>", line 1
    text = fb"Binary {text}"
           ^^
SyntaxError: 'b' and 'f' prefixes are incompatible

Теперь причина понятна сразу: префиксы 'b' и 'f' несовместимы.

  • f-строки должны вычислять выражения и преобразовывать их в строковые представления, а значит, работают только с текстом.

  • bytes — это не текст, а последовательность байтов, поэтому они не могут участвовать в системе форматирования строк.

6. Последовательные ошибки при распаковке значений

В Python есть удобный механизм распаковки итерируемых объектов: можно за одну строчку присвоить несколько значений сразу нескольким переменным. Но здесь есть правило: количество переменных слева должно совпадать с количеством значений справа. Если оно нарушается, возникает ValueError.

Раньше сообщения об ошибках вели себя непоследовательно:

>>> dog, age, city, hobbies = ["Frieda", 8, "Berlin"]
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
    dog, age, city, hobbies = ["Frieda", 8, "Berlin"]
    ^^^^^^^^^^^^^^^^^^^^^^^
ValueError: not enough values to unpack (expected 4, got 3)

В этом случае всё понятно: Python ожидал 4 значения, а получил 3. Сообщение ясно указывает и на ожидаемое, и на фактическое количество.

>>> dog, age = ["Frieda", 8, "Berlin"]
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    dog, age = ["Frieda", 8, "Berlin"]
    ^^^^^^^^
ValueError: too many values to unpack (expected 2)

Здесь Python 3.13 сообщает, что значений оказалось слишком много и что ожидалось 2. Но сколько именно было передано — не указывает. Полезно лишь частично.

Python 3.14 исправляет это упущение:

>>> dog, age = ["Frieda", 8, "Berlin"]
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
    dog, age = ["Frieda", 8, "Berlin"]
    ^^^^^^^^
ValueError: too many values to unpack (expected 2, got 3)

Теперь любое сообщение об ошибке при распаковке показывает обе цифры: ожидалось X, получено Y. Это делает диагностику последовательной и сразу видно, какую сторону нужно исправить — добавить переменные или убрать лишние значения.

7. Улучшенные ошибки при использовании as

Ключевое слово as создаёт псевдоним или альтернативное имя для модуля, исключения или контекстного менеджера. Его также можно использовать для перехвата подшаблона в структурном сопоставлении с образцом. Особенно часто as встречается в инструкциях import.

Внутри всё устроено так: as создаёт привязку в локальном пространстве имён, по сути выполняя присваивание. Но в отличие от обычного присваивания через знак равенства (=), ключевое слово as принимает только простые идентификаторы.

Вот пример из Python 3.13, где вы могли бы попробовать импортировать модуль с псевдонимом в виде списка — например, чтобы динамически организовать импорты:

>>> import sys as [alias]
  File "<python-input-0>", line 1
    import sys as [alias
                  ^
SyntaxError: invalid syntax

В Python 3.13 каретка (^) указывает на проблему, но общее сообщение “invalid syntax” не объясняет, почему список нельзя использовать после as в инструкции import.

Python 3.14 даёт более точное объяснение:

>>> import sys as [alias]
  File "<python-input-0>", line 1
    import sys as [alias]
                  ^^^^^^^
SyntaxError: cannot use list as import target

Сообщение “cannot use list as import target” сразу говорит: списки недопустимы после as в import. Другими словами, псевдоним должен быть именем, которое может стать переменной в вашем пространстве имён.

Помимо import, улучшенные сообщения об ошибках полезны и в других случаях, где используется as. Например, в блоках try ... except или при структурном сопоставлении с образцом.

8. Контекстные ошибки при использовании не-хешируемых типов

В Python объект считается хешируемым, если его хеш остаётся постоянным на протяжении всей жизни объекта.

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

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

>>> grid = {[0, 0]: "Label", [0, 1]: "Input"}
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
    grid = {[0, 0]: "Label", [0, 1]: "Input"}
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: unhashable type: 'list'

Сообщение об ошибке в Python 3.13 сообщает, что списки не являются хешируемыми. Это технически верно, но мало помогает, если вы не знаете, что такое хешируемость и почему она важна.

Python 3.14 добавляет важный контекст, который связывает ошибку с вашим действием:

>>> grid = {[0, 0]: "Label", [0, 1]: "Input"}
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
    grid = {[0, 0]: "Label", [0, 1]: "Input"}
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: cannot use 'list' as a dict key (unhashable type: 'list')

Улучшенное сообщение TypeError — “cannot use 'list' as a dict key (unhashable type: 'list')” — объясняет и то, что именно вы пытались сделать, и почему это не работает:

  • Проблема: вы пытаетесь использовать список как ключ словаря.

  • Почему не работает: списки в Python не являются хешируемыми.

В Python 3.14 это сообщение сразу подсказывает решение. Если списки нельзя использовать как ключи словаря, потому что они не хешируемы, значит нужен хешируемый тип, например tuple:

>>> grid = {(0, 0): "Label", (0, 1): "Input"}

Поскольку tuple в Python неизменяемы, они хешируемы и подходят для использования в качестве ключей словаря.

Этот подход с улучшенными, контекстными сообщениями об ошибках в Python 3.14 распространяется не только на ошибки типов, но и на арифметические операции.

9. Описательные ошибки в математических доменах

Математические функции имеют строго определённые области значений, на которых они корректно работают. Если передать функции из модуля math в Python значение, выходящее за эти пределы, вы получите ValueError. Однако традиционно сообщения об ошибках были не слишком информативными.

В Python 3.13, если попробовать вычислить квадратный корень из отрицательного числа, выводится лишь общее сообщение:

>>> import math
>>> math.sqrt(-1)
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    math.sqrt(-1)
    ~~~~~~~~~^^^^
ValueError: math domain error

Сообщение “math domain error” формально верно, но не объясняет, в чём именно проблема. Из него ясно только то, что нарушена область допустимых значений, но не указано, какая именно область допустима и какое значение вы передали.

В Python 3.14 это сообщение стало конкретным и полезным:

>>> import math
>>> math.sqrt(-1)
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    math.sqrt(-1)
    ~~~~~~~~~^^^^
ValueError: expected a nonnegative input, got -1.0

Теперь сообщение “expected a nonnegative input, got -1.0” прямо говорит, что функция требует неотрицательное число, и показывает, что именно вы передали. Чтобы исправить данные, можно взять модуль числа или использовать модуль cmath для поддержки комплексных чисел.

10. Ошибки при неправильном использовании async with

У инструкции with есть асинхронный вариант — async with. Он часто используется в асинхронном коде, так как многие операции ввода-вывода требуют фаз инициализации и завершения.

Представим, что вы пишете асинхронный веб-скрейпер и хотите управлять несколькими параллельными задачами. Для этого идеально подходит asyncio.TaskGroup. Но по привычке вы используете обычный with:

>>> import asyncio
>>> async def process_data():
...     with asyncio.TaskGroup() as tg:
...         pass
...
>>> asyncio.run(process_data())
Traceback (most recent call last):
  ...
  File "<python-input-0>", line 3, in process_data
    with asyncio.TaskGroup() as tg:
         ~~~~~~~~~~~~~~~~~^^
TypeError: 'TaskGroup' object does not support the context manager protocol

В Python 3.13 сообщение сообщает, что TaskGroup не поддерживает протокол контекстного менеджера. Это звучит сбивающе и на самом деле неверно.

TaskGroup действительно поддерживает протокол контекстного менеджера — но асинхронного. Он реализует методы .__aenter__() и .__aexit__(), а не .__enter__() и .__exit__().

Python 3.14 распознаёт эту типичную ошибку (использование with вместо async with) и даёт расширенное объяснение:

>>> import asyncio
>>> async def process_data():
...      with asyncio.TaskGroup() as tg:
...           pass
...
>>> asyncio.run(process_data())
Traceback (most recent call last):
  ...
  File "<python-input-0>", line 3, in process_data
    with asyncio.TaskGroup() as tg:
         ~~~~~~~~~~~~~~~~~^^
TypeError: 'asyncio.taskgroups.TaskGroup' object does not support
⮑ the context manager protocol (missed __exit__ method)
⮑ but it supports the asynchronous context manager protocol.
⮑ Did you mean to use 'async with'?

Подсказка “Did you mean to use 'async with'?” сразу указывает на решение. Теперь сообщение корректно отмечает, что TaskGroup действительно является контекстным менеджером, но не того типа, который вы использовали.

Такая же проверка работает и в обратную сторону — если использовать async with для синхронного контекстного менеджера:

>>> import asyncio
>>> async def read_file():
...     async with open("data.txt") as f:
...         return f.read()
...
>>> asyncio.run(read_file())
Traceback (most recent call last):
  ...
  File "<python-input-0>", line 3, in read_file
    async with open("data.txt") as f:
               ~~~~^^^^^^^^^^^^
TypeError: '_io.TextIOWrapper' object does not support
⮑ the asynchronous context manager protocol (missed __aexit__ method)
⮑ but it supports the context manager protocol.
⮑ Did you mean to use 'with'?

В этом случае вы пытаетесь применить async with к обычному файловому объекту, который поддерживает только синхронный протокол. Ошибка правильно подсказывает, что нужно испол��зовать обычный with.

Русскоязычное сообщество про Python

Друзья! Эту статью подготовила команда Python for Devs — канала, где каждый день выходят самые свежие и полезные материалы о Python и его экосистеме. Подписывайтесь, чтобы ничего не пропустить!

Заключение

Улучшенные сообщения об ошибках в Python 3.14 — это не просто более удобная отладка. Это отражение постоянного стремления Python быть языком, который обучает прямо во время выполнения. Десять новых улучшений превратили запутанные сообщения в полезные подсказки, которые не только указывают путь к решению, но и помогают глубже понять принципы устройства Python.

В этом туториале вы узнали, как Python 3.14 улучшает сообщения об ошибках для:

  • распространённых синтаксических ошибок — предлагая правильные ключевые слова и объясняя порядок блоков в условных цепочках,

  • строк и байтовых литералов — указывая на недопустимые комбинации префиксов и поясняя, когда кавычки могут быть частью содержимого,

  • распаковки и операций присваивания — показывая ожидаемое и фактическое количество значений и объясняя, какие цели допустимы для присваивания,

  • операций, связанных с типами — давая контекст о требованиях к хешируемости в множествах и словарях, а также о математических ограничениях домена,

  • использования контекстных менеджеров — различая синхронные и асинхронные протоколы и подсказывая подходящий синтаксис.

Когда вы встретите эти новые сообщения в своём коде, вы оцените, что они ведут к быстрому исправлению, а не оставляют вас разгадывать туманные намёки. Полный список изменений смотрите в официальной документации What’s New in Python 3.14.

Комментарии (0)