Все статьи | Разработка

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

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

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

Python 3.10

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


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

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

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 для разнесения на несколько строк определения коллекции контекстных менеджеров. Тут же можно добавить, что теперь разрешается оставлять запятую после работы с финальным контекстным менеджером в группе:


   with (
       CtxManager1() as example1,
       CtxManager2() as example2,
       CtxManager3() as example3,
   ):
       ...

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

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

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


   >>> 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% при работе с мелкими объектами.

Читайте также: Современный PHP сформирует хорошее мышление в стиле ООП: интервью с Максимом Шамаевым

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

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

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

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

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.

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.

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:

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 выглядит уже не очень лаконично.

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:

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 .

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

{
  "action": "copy",
  "args": ["/old/path", "new_path"],
}

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

{
  "action": "delete",
  "args": ["/old/path"],
}

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

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 можно посмотреть все детали нового синтаксиса.

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

Аватар пользователя Святослав Иванов
Святослав Иванов 27 октября 2021
Рекомендуемые программы

С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.

Иконка программы Фронтенд-разработчик
Профессия
Разработка фронтенд-компонентов веб-приложений
8 декабря 8 месяцев
Иконка программы Python-разработчик
Профессия
Разработка веб-приложений на Django
8 декабря 8 месяцев
Иконка программы PHP-разработчик
Профессия
Разработка веб-приложений на Laravel
8 декабря 8 месяцев
Иконка программы Node.js-разработчик
Профессия
Разработка бэкенд-компонентов веб-приложений
8 декабря 8 месяцев
Иконка программы Верстальщик
Профессия
Вёрстка с использованием последних стандартов CSS
в любое время 5 месяцев
Иконка программы Java-разработчик
Профессия
Разработка приложений на языке Java
8 декабря 10 месяцев
Иконка программы Разработчик на Ruby on Rails
Профессия
Новый
Создает веб-приложения со скоростью света
8 декабря 5 месяцев