/
Блог Хекслета
/
Код
/

Вышла новая версия языка программирования Python 3.10. Что в ней важного?

Вышла новая версия языка программирования Python 3.10. Что в ней важного?

27 октября 2021 г.

4 минуты
279
11
Вышла новая версия языка программирования Python 3.10. Что в ней важного?

Разработчики представили новую версию языка программирования Python 3.10 — она будет поддерживаться не менее следующих пяти лет. Мы вместе с преподавателем Хекслета Валентином Хомутенко рассказываем, что нового в версии Python 3.10 и почему ее так долго ждали разработчики.

Содержание

Python 3.10

— В модуль typing был добавлен оператор Concatenate вместе с переменной ParamSpec, это позволит передавать дополнительную информацию для статической проверки типов при использовании Callable. Модуль typing теперь также имеет специальные значения TypeGuard для аннотирования функций защиты типов и TypeAlias для явного определения псевдонима типа.

1
   StrCache: TypeAlias = 'Cache[str]'  # a type alias

Кроме того, в модуль typing также добавлен новый оператор, позволяющий использовать синтаксис "X | Y" для выбора одного из типов (тип X или тип Y).

1
2
3
4
5
6
7
def square(number: int | float) -> int | float:
      return number ** 2

эквивалентно ранее поддерживаемой конструкции:

def square(number: Union[int, float]) -> Union[int, float]:
      return number ** 2

— В Python 3.10 появилась возможность использовать круглые скобки в операторе with для разнесения на несколько строк определения коллекции контекстных менеджеров. Тут же можно добавить, что теперь разрешается оставлять запятую после работы с финальным контекстным менеджером в группе:

1
2
3
4
5
6
   with (
       CtxManager1() as example1,
       CtxManager2() as example2,
       CtxManager3() as example3,
   ):
       ...

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

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

— Функция zip() теперь имеет необязательный флаг "strict", при указании которого проводится проверка на одинаковую длину перебираемых аргументов.

1
2
3
4
5
6
7
   >>> list(zip(('a', 'b', 'c'), (1, 2, 3), strict=True))
   [('a', 1), ('b', 2), ('c', 3)]

   >>> list(zip(range(3), ['fee', 'fi', 'fo', 'fum'], strict=True))
   Traceback (most recent call last):
   ...
   ValueError: zip() argument 2 is longer than argument 1

Среди других обновлений в функциях — появились встроенные функции aiter() и anext() с реализацией асинхронных аналогов функциям iter() и next(). Кроме того, работа конструкторов str(), bytes() и bytearray() была увеличена на 40% при работе с мелкими объектами.

Pattern matching как главное в новом релизе

По словам преподавателя Хекслета Валентина Хомутенко, главное обновление этого релиза — функция pattern matching. Вы могли где-то слышать, что «в Python теперь есть switch case», но это не совсем так. Pattern matching — намного круче, чем обычный switch в других языках.

Предположим, что нам нужно написать функцию, которая читает содержимое текстовых файлов. При этом если файл имеет расширение .md, то она должна попробовать прочитать его как markdown-документ. Если расширение .docx, то как документ Microsoft Office. Во всех остальных случаях надо прочитать его как обычный текстовый файл.

Такую логику можно реализовать помощью обычного if.

1
2
3
4
5
6
7
8
9
10
import pathlib

def read_document(file_path):
    with open(file_path) as file:
        extension = pathlib.Path(file_path).suffix.lower():
        if extension == '.md':
            return parse_md(file)
        if extension == '.docx':
          	return parse_docx(file)
        return parse_plain(file)

А вот как такая же задача решается с помощью pattern matching.

1
2
3
4
5
6
7
8
9
10
11
import pathlib

def read_document(file_path):
    with open(file_path) as file:
        match pathlib.Path(file_path).suffix.lower():
            case '.md':
                return parse_md(file)
            case '.docx':
                return parse_docx(file)
            case _:
                return parse_plain(file)

Окей, стало чуть компактнее, но кажется не критично.

После этого мы поняли, что пользователи часто передают пути к картинкам и другим бинарным файлам, которые нельзя прочитать как обычный текст. Давайте модифицируем функцию так, чтобы как обычный текст читались только файлы с расширениями .txt или .log.

1
2
3
4
5
6
7
8
9
10
11
12
import pathlib

def read_document(file_path):
    with open(file_path) as file:
        extension = pathlib.Path(file_path).suffix.lower():
        if extension == '.md':
            return parse_md(file)
        if extension == '.docx':
            return parse_docx(file)
        if extension in ['.txt', '.log']:
            return parse_plain(file)
        raise ValueError('unsupported extension {}'.format(extension))

И вариант с pattern matching:

1
2
3
4
5
6
7
8
9
10
11
12
13
import pathlib

def read_document(file_path):
    with open(file_path) as file:
        match pathlib.Path(file_path).suffix.lower():
            case '.md':
                return parse_md(file)
            case '.docx':
                return parse_docx(file)
            case '.txt' | '.log':
                return parse_plain(file)
            case _ as extension:
                raise ValueError('unsupported extension {}'.format(extension))

Обратите внимание, что мы обработали сразу два варианта .txt и .log в одной ветке case, используя оператор |. Уже выглядит получше, особенно если сравнить с обычными if.

Помимо проверки на конкретное значение, теперь мы можем совершать и более сложные штуки. Например, мы разрабатываем утилиту для работы с файлами. Нам нужно обработать одну из команд пользователя: «скопировать файл» или «удалить файл». Команда копирования выглядит как copy old_path new_path, а удаления — delete path.

Код с обычными if выглядит уже не очень лаконично.

1
2
3
4
5
6
7
8
9
def handle_command(command):
    command_parts = command.split():

    if len(command_parts) == 3 and command_parts[0] == 'copy':
        copy(command_parts[1], command_parts[2])
    elif len(command_parts) == 2 and command_parts[0] == 'delete':
        delete(path)
    case _:
        raise ValueError('unsupported command')

А вот так выглядит решение с pattern matching:

1
2
3
4
5
6
7
8
9
def handle_command(command):
    # делим строку по пробелам, чтобы получить составные части
    match command.split():
        case ['copy', old_path, new_path]:
            copy(old_path, new_path)
        case ['delete', path]:
            delete(path)
        case _:
            raise ValueError('unsupported command')

Pattern matching позволяет здесь проверять сразу 2 вещи: количество элементов списка и конкретное значение первого элемента. И все это в рамках одной ветки case без лишних вложенностей или соединения условий через or / and .

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

1
2
3
4
{
  "action": "copy",
  "args": ["/old/path", "new_path"],
}

А команда удаление так:

1
2
3
4
{
  "action": "delete",
  "args": ["/old/path"],
}

Код обработки команды не стал сильно сложнее, если мы используем pattern matching.

1
2
3
4
5
6
7
8
def handle_command(command):
    match command:
        case {"action": "copy", "args": [old_path, new_path]}:
            copy(old_path, new_path)
        case {"action": "delete", "args": [path]}:
            delete(path)
        case _:
            raise ValueError('unsupported command')

И опять же pattern matching позволяет нам сделать сразу много проверок в одном выражении:

  • Существование ключа action
  • Значение ключа action — copy или delete
  • Существование ключа args
  • Тип значения у ключа args — значение должно быть массивом.
  • Количество элементов внутри args.

И все это в одном выражении case :tada:. Если вы хотите подробнее разобраться в этом и прочувствовать всю магию этой функции, то попробуйте в качестве упражнения переписать этот пример без использования pattern matching.

Больше примеров использования можно увидеть в PEP636, а в PEP634 можно посмотреть все детали нового синтаксиса.

Svet Ivanov

5 лет назад

11

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845