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



Книга Что нового в Python 3.10?

Разработка 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
"
]
}
],
"source": [
"http_code = \"418\"
",
"
",
"if http_code == \"200\":
",
" print(\"OK\")
",
"elif http_code == \"404\":
",
" print(\"Not Found\")
",
"elif http_code == \"418\":
",
" print(\"I'm a teapot\")
",
"else:
",
" 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\"
",
"
",
"match http_code:
",
" case \"200\":
",
" print(\"OK\")
",
" case \"404\":
",
" print(\"Not Found\")
",
" case \"418\":
",
" print(\"I'm a teapot\")
",
" case _:
",
" 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 = {
",
" 'id': 1,
",
" 'meta': {
",
" 'source': 'abc',
",
" 'location': 'west'
",
" }
",
"}"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"dict_b = {
",
" 'id': 2,
",
" 'source': 'def',
",
" 'location': 'west'
",
"}"
]
}
]
}

Затем напишем шаблон для 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
",
"{
",
" 'id': int,
",
" 'meta': {'source': str,
",
" 'location': str}
",
"}
",
"```"
],
"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
",
"{
",
" 'id': int,
",
" 'source': str,
",
" 'location': str
",
"}
",
"```"
],
"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
2 def west
no match
"
]
}
],
"source": [
"# в цикле проходятся оба словаря и 'test'
",
"for d in [dict_a, dict_b, 'test']:
",
" match d:
",
" case {'id': ident,
",
" 'meta': {'source': source,
",
" 'location': loc}}:
",
" print(ident, source, loc)
",
" case {'id': ident,
",
" 'source': source,
",
" 'location': loc}:
",
" print(ident, source, loc)
",
" case _:
",
" 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:
",
" 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, \\
",
" open('file2.txt', 'w') as fout:
",
" 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,
",
" open('file2.txt', 'w') as fout):
",
" 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,
",
" open('file2.txt', 'w') as fout):
",
" 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
",
"
",
"def add(x: Union[int, float], y: Union[int, float]):
",
" 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):
",
" return x + y"
]
}
]
}

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


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


SyntaxError: unexpected EOF while parsing

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


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

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



Недостающая скобка в 3.9 и 3.10. По ссылке здесь смотрите все три сравнения.


Незакрытый строковый литерал в 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 доступна для скачивания здесь.


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


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


1536   0  

Comments

    Ничего не найдено.