Взгляд на новые функции Python 3.11

По данным Python Software Foundation (PSF), Python 3.11 находится в 7-й альфа-версии, выпуск которой запланирован на октябрь 2022 года.

Какие изменения включены в следующую версию?

Пролог

Чтобы оценить различия между версиями 3.10 и 3.11, я установил 2 контейнера докеров.

Первый контейнер для версии 3.10:

docker run -t -d python:3.10.4-bullseye

Второй контейнер для версии 3.11:

docker run -t -d python:3.11-rc-bullseye

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

Затем я могу выполнить свой код Python в двух средах и увидеть разницу.

В следующих разделах я сначала покажу пример кода, а затем покажу разницу между двумя версиями.

1: Местоположение ошибки

Вывод в версии 3.10:

1
100
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/local/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/root/py310/myapp/__main__.py", line 4, in <module>
    print(d["key_11"])
KeyError: 'key_11'

Вывод в версии 3.11

1
100
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/root/py311/myapp/__main__.py", line 4, in <module>
    print(d["key_11"])
          ~^^^^^^^^^^
KeyError: 'key_11'

Python 3.11 раскрывает разработчику лучшее местонахождение ошибок, что делает процесс разработки потрясающим.

2: Тип ‘self’ type

Тип self уже был представлен в модуле typing extensions, и теперь он продвигается в стандартную библиотеку типизации.

Приведенный выше код представляет структуру каталога. Каталоги имеют подкаталоги, поэтому определение является рекурсивным.

Вывод в Python 3.10:

Traceback (most recent call last):
  File "/usr/local/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/local/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/root/py310/myapp/__main__.py", line 2, in <module>
    from typing import List, Tuple, Self

Вывод в Python 3.11:

{'content': (['a.txt', 'b.txt'],
             [{'content': (['file1', 'file2'], None), 'name': 'dir1'}]),
 'name': 'dir2'}

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

Чтобы этот код работал в 3.11 и 3.10, вы можете выполнить импорт следующим образом:

try:
    from typing import Self
except ImportError:
    from typing_extensions import Self

3: Примечание об исключении

Класс BaseException теперь имеет атрибут класса __note__, по умолчанию равный None.
Вы можете переопределить его любой строкой по вашему выбору и предоставить дополнительную информацию.

Вывод в Python 3.10:

Traceback (most recent call last):
  File "/usr/local/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/local/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/root/py310/myapp/__main__.py", line 6, in <module>
    raise MyException("some exception")
__main__.MyException: some exception

Вывод в Python 3.11:

Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/root/py311/myapp/__main__.py", line 6, in <module>
    raise MyException("some exception")
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
MyException: some exception
this is my note :)

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

4: Группы исключений

В версии 3.11 представлено новое имя типа исключения ExceptionGroup для создания набора исключений и их обработки в предложении except.

В дополнение к этому вводится новый синтаксис except*.

Вывод в Python 3.10:

File "/usr/local/lib/python3.10/runpy.py", line 189, in _run_module_as_main
    mod_name, mod_spec, code = _get_main_module_details(_Error)
  File "/usr/local/lib/python3.10/runpy.py", line 223, in _get_main_module_details
    return _get_module_details(main_name)
  File "/usr/local/lib/python3.10/runpy.py", line 157, in _get_module_details
    code = loader.get_code(mod_name)
  File "<frozen importlib._bootstrap_external>", line 1017, in get_code
  File "<frozen importlib._bootstrap_external>", line 947, in source_to_code
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/root/py310/myapp/__main__.py", line 18
    except * (ToYoungException, EmailIsInvalidException) as exception_group_1:

Вывод в Python 3.11:

validations failed
  + Exception Group Traceback (most recent call last):
  |   File "<frozen runpy>", line 198, in _run_module_as_main
  |   File "<frozen runpy>", line 88, in _run_code
  | ExceptionGroup:  (1 sub-exception)
  +-+---------------- 1 ----------------
    | Exception Group Traceback (most recent call last):
    |   File "/root/py311/myapp/__main__.py", line 14, in <module>
    |     raise ExceptionGroup(
    |     ^^^^^^^^^^^^^^^^^^^^^
    | ExceptionGroup: Data validations (2 sub-exceptions)
    +-+---------------- 1 ----------------
      | ToYoungException: Age must be over 18 - age is 11
      +---------------- 2 ----------------
      | EmailIsInvalidException: Email must be valid some_wannabe_email
      +------------------------------------
    | 
    | The above exception was the direct cause of the following exception:
    | 
    | Traceback (most recent call last):
    |   File "/root/py311/myapp/__main__.py", line 20, in <module>
    |     raise ValueError from exception_group_1
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    | ValueError
    +------------------------------------

Как мы видим, это очень ценно, когда у нас есть несколько причин отказа, которые мы хотим раскрыть за один раз.

5. Вложенные асинхронные понимания

Вывод в Python 3.10:

Traceback (most recent call last):
  File "/usr/local/lib/python3.10/runpy.py", line 189, in _run_module_as_main
    mod_name, mod_spec, code = _get_main_module_details(_Error)
  File "/usr/local/lib/python3.10/runpy.py", line 223, in _get_main_module_details
    return _get_module_details(main_name)
  File "/usr/local/lib/python3.10/runpy.py", line 157, in _get_module_details
    code = loader.get_code(mod_name)
  File "<frozen importlib._bootstrap_external>", line 1017, in get_code
  File "<frozen importlib._bootstrap_external>", line 947, in source_to_code
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/root/py310/myapp/__main__.py", line 11
    return { n: [x async for x in elements(n)] for n in range(3)}
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: asynchronous comprehension outside of an asynchronous function

Вывод в Python 3.11:

{0: [1], 1: [1, 1], 2: [1, 2, 4], 3: [1, 3, 9, 27], 4: [1, 4, 16, 64, 256]}

В тот момент, когда код входит в блок обработки, он теперь знает об обработке в отношении своего текущего «контекста функции».
Если обработка не является асинхронной, то внутренний блок кода не может иметь асинхронных операторов.

Однако в Python 3.11 включения становятся неявно асинхронными, если они содержат внутренние операторы async , допускающие внутренние включения async.

6: парсер TOML

Синтаксический анализ TOML теперь является частью стандартной библиотеки, как JSON и CSV.

Вывод в Python 3.10:

Traceback (most recent call last):
  File "/usr/local/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/local/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/root/py310/myapp/__main__.py", line 2, in <module>
    import tomllib
ModuleNotFoundError: No module named 'tomllib'

Вывод в Python 3.11:

{'clients': {'data': [['gamma', 'delta'], [1, 2]], 'hosts': ['alpha', 'omega']},
 'database': {'connection_max': 5000,
              'enabled': True,
              'ports': [8000, 8001, 8002],
              'server': '192.168.1.1'},
 'owner': {'dob': datetime.datetime(1979, 5, 27, 7, 32, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=57600))),
           'name': 'Tom Preston-Werner'},
 'servers': {'alpha': {'dc': 'eqdc10', 'ip': '10.0.0.1'},
             'beta': {'dc': 'eqdc10', 'ip': '10.0.0.2'}},
 'title': 'TOML Example'}

7. Оптимизация производительности

Утверждается, что Python 3.11 на 10–60% быстрее во время выполнения по сравнению с Python 3.10. Подробнее об этом можно прочитать в разделе бенчмаркинга здесь.

Python 3.11 поставляется с набором улучшений, как в производительности, так и в удобстве для разработчиков.

Узнайте об этих обновлениях и улучшите свои навыки программирования!