Найти в Дзене
Nuances of programming

Что нового в Python 3.10?

Источник: Nuances of Programming Разработка Python 3.10 стабилизировалась, и пришло время наконец-то протестировать все те новые функции, которые войдут в финальный выпуск. Мы расскажем все самое интересное о том, что было добавлено в этот релиз Python: сопоставление структурных шаблонов, заключенные в скобки менеджеры контекста, нововведения, касающиеся типов, а также новые и улучшенные сообщения об ошибках. Сопоставление структурных шаблонов Сопоставление структурных шаблонов  —  невероятная и просто потрясающая функция, которая будет добавлена в Python. Возьмем оператор if-else, который выглядит вот так: {
"metadata": {
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3 },
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.0" },
"orig_nbformat": 2,
"kernelspec": {
"name": "python3",
"display_name": "Python 3.10.0 64-b
Оглавление

Источник: Nuances of Programming

Разработка Python 3.10 стабилизировалась, и пришло время наконец-то протестировать все те новые функции, которые войдут в финальный выпуск.

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

Сопоставление структурных шаблонов

Сопоставление структурных шаблонов  —  невероятная и просто потрясающая функция, которая будет добавлена в Python.

Возьмем оператор if-else, который выглядит вот так:

{
"metadata": {
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3 },
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.0" },
"orig_nbformat": 2,
"kernelspec": {
"name": "python3",
"display_name": "Python 3.10.0 64-bit",
"metadata": {
"interpreter": {
"hash": "0af001259c9085d3649b2fae0bd6b291d17df5c9f127f6973a1b7698b4048c39" }
}
}
},
"nbformat": 4,
"nbformat_minor": 2,
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"I'm a teapot\n" ]
}
],
"source": [
"http_code = \"418\"\n",
"\n",
"if http_code == \"200\":\n",
" print(\"OK\")\n",
"elif http_code == \"404\":\n",
" print(\"Not Found\")\n",
"elif http_code == \"418\":\n",
" print(\"I'm a teapot\")\n",
"else:\n",
" print(\"Code not found\")" ]
}
]
}

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

{
"metadata": {
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3 },
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": 3 },
"orig_nbformat": 2 },
"nbformat": 4,
"nbformat_minor": 2,
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"http_code = \"418\"\n",
"\n",
"match http_code:\n",
" case \"200\":\n",
" print(\"OK\")\n",
" case \"404\":\n",
" print(\"Not Found\")\n",
" case \"418\":\n",
" print(\"I'm a teapot\")\n",
" case _:\n",
" print(\"Code not found\")" ]
}
]
}

Это новый оператор match-case. Круто, но пока ничего особенного.

Особенным его делает сопоставление структурных шаблонов. Оно позволяет выполнять ту же логику match-case, но исходя из того, соответствует ли структура объекта сравнения заданному шаблону.

Определим теперь два словаря с разными структурами:

{
"metadata": {
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3 },
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.0" },
"orig_nbformat": 2,
"kernelspec": {
"name": "python3",
"display_name": "Python 3.10.0 64-bit",
"metadata": {
"interpreter": {
"hash": "0af001259c9085d3649b2fae0bd6b291d17df5c9f127f6973a1b7698b4048c39" }
}
}
},
"nbformat": 4,
"nbformat_minor": 2,
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"dict_a = {\n",
" 'id': 1,\n",
" 'meta': {\n",
" 'source': 'abc',\n",
" 'location': 'west'\n",
" }\n",
"}" ]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"dict_b = {\n",
" 'id': 2,\n",
" 'source': 'def',\n",
" 'location': 'west'\n",
"}" ]
}
]
}

Затем напишем шаблон для dict_a:

{
"metadata": {
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3 },
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.0" },
"orig_nbformat": 2,
"kernelspec": {
"name": "python3",
"display_name": "Python 3.10.0 64-bit",
"metadata": {
"interpreter": {
"hash": "0af001259c9085d3649b2fae0bd6b291d17df5c9f127f6973a1b7698b4048c39" }
}
}
},
"nbformat": 4,
"nbformat_minor": 2,
"cells": [
{
"source": [
"```python\n",
"{\n",
" 'id': int,\n",
" 'meta': {'source': str,\n",
" 'location': str}\n",
"}\n",
"```" ],
"cell_type": "markdown",
"metadata": {}
}
]
}

И шаблон для dict_b тоже:

{
"metadata": {
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3 },
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": 3 },
"orig_nbformat": 2 },
"nbformat": 4,
"nbformat_minor": 2,
"cells": [
{
"source": [
"```python\n",
"{\n",
" 'id': int,\n",
" 'source': str,\n",
" 'location': str\n",
"}\n",
"```" ],
"cell_type": "markdown",
"metadata": {}
}
]
}

Поместим их вместе в оператор match-case, и вместе со всем тем, что было в else, а фактически уже в case _, мы получим:

{
"metadata": {
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3 },
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.0" },
"orig_nbformat": 2,
"kernelspec": {
"name": "python3",
"display_name": "Python 3.10.0 64-bit",
"metadata": {
"interpreter": {
"hash": "0af001259c9085d3649b2fae0bd6b291d17df5c9f127f6973a1b7698b4048c39" }
}
}
},
"nbformat": 4,
"nbformat_minor": 2,
"cells": [
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"1 abc west\n2 def west\nno match\n" ]
}
],
"source": [
"# в цикле проходятся оба словаря и 'test'\n",
"for d in [dict_a, dict_b, 'test']:\n",
" match d:\n",
" case {'id': ident,\n",
" 'meta': {'source': source,\n",
" 'location': loc}}:\n",
" print(ident, source, loc)\n",
" case {'id': ident,\n",
" 'source': source,\n",
" 'location': loc}:\n",
" print(ident, source, loc)\n",
" case _:\n",
" print('no match')" ]
}
]
}

Особенно полезно это для обработки данных (пример смотрите на этом видео на 15:23).

Заключенные в скобки менеджеры контекста

Это небольшое изменение связано с куда более крупным нововведением, появившемся с версии Python 3.9  —  новым парсером PEG.

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

С появлением в Python 3.9 парсера PEG эти барьеры были устранены, что в долгосрочной перспективе способно привести к более элегантному синтаксису. И первой ласточкой здесь стали новые, заключенные в скобки менеджеры контекста.

До Python 3.9 для открытия двух (или большего числа) потоков файлового ввода-вывода мы писали что-то вроде этого:

{
"metadata": {
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3 },
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.0" },
"orig_nbformat": 2,
"kernelspec": {
"name": "python3",
"display_name": "Python 3.10.0 64-bit",
"metadata": {
"interpreter": {
"hash": "0af001259c9085d3649b2fae0bd6b291d17df5c9f127f6973a1b7698b4048c39" }
}
}
},
"nbformat": 4,
"nbformat_minor": 2,
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"with open('file1.txt', 'r') as fin, open('file2.txt', 'w') as fout:\n",
" fout.write(fin.read())" ]
}
]
}

До появления нового парсера мы использовали два контекстных менеджера, но только они оба должны были находиться на одной строке.

Эта первая строка довольно длинная, и даже слишком. Но из-за ограничений синтаксического анализатора единственным способом разделения этой строки на несколько строк было использование \ (символа продолжения строки):

{
"metadata": {
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3 },
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.0" },
"orig_nbformat": 2,
"kernelspec": {
"name": "python3",
"display_name": "Python 3.10.0 64-bit",
"metadata": {
"interpreter": {
"hash": "0af001259c9085d3649b2fae0bd6b291d17df5c9f127f6973a1b7698b4048c39" }
}
}
},
"nbformat": 4,
"nbformat_minor": 2,
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"with open('file1.txt', 'r') as fin, \\\n",
" open('file2.txt', 'w') as fout:\n",
" fout.write(fin.read())" ]
}
]
}

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

{
"metadata": {
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3 },
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.0" },
"orig_nbformat": 2,
"kernelspec": {
"name": "python3",
"display_name": "Python 3.10.0 64-bit",
"metadata": {
"interpreter": {
"hash": "0af001259c9085d3649b2fae0bd6b291d17df5c9f127f6973a1b7698b4048c39" }
}
}
},
"nbformat": 4,
"nbformat_minor": 2,
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"with (open('file1.txt', 'r') as fin,\n",
" open('file2.txt', 'w') as fout):\n",
" fout.write(fin.read())" ]
}
]
}

А это как раз для Python характерно.

Прежде чем идти дальше, еще раз отметим одну маленькую странность в этой новой функции: она не совсем новая.

Посмотрите:

{
"metadata": {
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3 },
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.0" },
"orig_nbformat": 2,
"kernelspec": {
"name": "python3",
"display_name": "Python 3.10.0 64-bit",
"metadata": {
"interpreter": {
"hash": "0af001259c9085d3649b2fae0bd6b291d17df5c9f127f6973a1b7698b4048c39" }
}
}
},
"nbformat": 4,
"nbformat_minor": 2,
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"with (open('file1.txt', 'r') as fin,\n",
" open('file2.txt', 'w') as fout):\n",
" fout.write(fin.read())" ]
}
]
}

В Python 3.9 она работает. А все потому, что новый синтаксический анализатор позволял использовать этот синтаксис, хотя и не поддерживался официально до Python 3.10.

Нововведения, касающиеся типов

Обновления в Python затронули и типы.

Самое интересное нововведение здесь  —  это включение нового оператора, который ведет себя как логическое ИЛИ для типов. Раньше для этого мы использовали метод Union:

{
"metadata": {
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3 },
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.0" },
"orig_nbformat": 2,
"kernelspec": {
"name": "python3",
"display_name": "Python 3.10.0 64-bit",
"metadata": {
"interpreter": {
"hash": "0af001259c9085d3649b2fae0bd6b291d17df5c9f127f6973a1b7698b4048c39" }
}
}
},
"nbformat": 4,
"nbformat_minor": 2,
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from typing import Union\n",
"\n",
"def add(x: Union[int, float], y: Union[int, float]):\n",
" return x + y" ]
}
]
}

Теперь не нужно писать from typing import Union, а вместо Union[int, float] осталось int | float. Так что сейчас он выглядит намного чище:

{
"metadata": {
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3 },
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.0" },
"orig_nbformat": 2,
"kernelspec": {
"name": "python3",
"display_name": "Python 3.10.0 64-bit",
"metadata": {
"interpreter": {
"hash": "0af001259c9085d3649b2fae0bd6b291d17df5c9f127f6973a1b7698b4048c39" }
}
}
},
"nbformat": 4,
"nbformat_minor": 2,
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"def add(x: int | float, y: int | float):\n",
" return x + y" ]
}
]
}

Улучшенные сообщения об ошибках

Скорее всего, вы сразу бросились к поисковику, впервые увидев такую ошибку:

SyntaxError: unexpected EOF while parsing

Самый первый результат в выдаче поисковика при вводе SyntaxError оказывается тем, что многие из нас, конечно, и предполагали:

Неожиданный конец файла во время анализа  — *простой и элегантный* способ сообщить о пропущенной скобке
Неожиданный конец файла во время анализа  — *простой и элегантный* способ сообщить о пропущенной скобке

Не самое четкое сообщение об ошибке, и в Python таких много. К счастью, кто-то обратил на это внимание, и многие из этих сообщений значительно улучшились:

-3
Недостающая скобка в 3.9 и 3.10. По ссылке здесь смотрите все три сравнения.
Недостающая скобка в 3.9 и 3.10. По ссылке здесь смотрите все три сравнения.
-5
Незакрытый строковый литерал в 3.9 и 3.10
Незакрытый строковый литерал в 3.9 и 3.10
-7
Вместо оператора сравнения использован оператор присваивания в 3.9 и 3.10
Вместо оператора сравнения использован оператор присваивания в 3.9 и 3.10

Есть еще несколько изменений, перечисленных в официальном списке изменений, но не показанных во время тестирования, в том числе:

from collections import namedtoplo

> AttributeError: module 'collections' has no attribute 'namedtoplo'. Did you mean: namedtuple?

Здесь AttributeError такой же, как и раньше, но с уже введенным именем атрибута namedtoplo, в котором допущена опечатка (правильное название атрибута  —  namedtuple).

Подобные улучшения замечены и с сообщениями об ошибке NameError:

new_var = 5 print(new_vr)

> NameError: name 'new_vr' is not defined. Did you mean: new_var?

Есть много и других обновлений сообщений об ошибках! Ознакомьтесь со всеми ими здесь.

Вот и все. Это были основные новые функции, которые появятся в Python 3.10!

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

Хотите ознакомиться самостоятельно? Бета-версия 3.10.0b1 доступна для скачивания здесь.

Надеюсь, статья вам понравилась. Спасибо за внимание.

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

Читайте также:

Читайте нас в Telegram, VK

Перевод статьи James Briggs: What’s New in Python 3.10?