Все важные фичи и изменения в Python 3.10

Автор оригинала: Martin Heinz
  • Перевод

Если вам хочется попробовать все фичи великолепной последний версии Python, нужно установить альфа или бета-версию. Однако учитывая, что эти версии не стабильны, мы не хотим перезаписывать дефолтную установку языка. Будем устанавливать альфу Python 3.10 рядом с текущим интерпретатором. И в преддверии старта нового потока курса Fullstack-разработчик на Python — обозревать все новшества новой версии языка.


Сделать это можно выполнив эти команды:

wget https://www.python.org/ftp/python/3.10.0/Python-3.10.0a6.tgz
tar xzvf Python-3.10.0a6.tgz
cd Python-3.10.0a6
./configure --prefix=$HOME/python-3.10.0a6
make
make install
$HOME/python-3.10.0a6/bin/python3.10

После запуска кода выше вы увидите приветствие от среды разработки IDLE:

Python 3.10.0a6 (default, Mar 27 2021, 11:50:33) [GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

А теперь, с установленным Python, мы можем посмотреть на все новые фичи и изменения.

Улучшения в проверке типов

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

# Function that accepts either `int` or `float`
# Old:
def func(value: Union[int, float]) -> Union[int, float]:
    return value

# New:
def func(value: int | float) -> int | float:
    return value

Кроме того, это простое улучшение не ограничивается только аннотациями типа, оно может применяться с функциями isinstance() и issubclass():

isinstance("hello", int | str)
# True

Изменения синтаксиса псевдонима типа

В более ранних версиях Python добавлены псевдонимы типов, позволяющие создавать синонимы пользовательских классов. В Python 3.9 и более ранних версиях псевдонимы записывались так:

FileName = str

def parse(file: FileName) -> None:
    ...

Здесь FileName - псевдоним базового типа строки Python. Однако, начиная с Python 3.10, синтаксис определения псевдонимов типов будет изменён:

FileName: TypeAlias = str

def parse(file: FileName) -> None:
    ...

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

Кроме этих 2 изменений появилось другое улучшение модуля typing - в предложениях по улучшению номер 612 оно называется Parameter Specification Variables. Однако это не то, что вы найдете в основной массе кода на Python, поскольку эта функциональность используется для пересылки параметра типов от одного вызываемого типа к другому вызываемому типу, например, в декораторах. Если вам есть где применить эту функциональность, посмотрите её PEP.

bit_count()

Начиная с Python 3.10, чтобы посчитать количество битов в двоичном представлении целого числа, можно вызвать int.bit_count(). Функция известна как Population Count (popcount):

value = 42
print(bin(value))
# '0b101010'
print(value.bit_count())
# 3

Это, безусловно, хорошо, но давайте будем реалистами: реализация этой функции не так уж сложна, на самом деле это всего лишь одна строка:

def bit_count(value):
    return bin(value).count("1")

При этом popcount() — ещё одна удобная функция, она может пригодиться в какой-то момент; всевозможные полезные маленькие функции — одна из причин, по которой Python так популярен: на первый взгляд всё доступно из коробки.

Модуль distutils устарел

В новой версии функции не только добавляются, но также удаляются или объявляются устаревшими. Это касается пакета distutils, который объявлен устаревшим в 3.10 и будет удален в 3.12. На какое-то время его заменили пакетами setuptools и packaging, поэтому если вы работаете с каким-то из этих пакетов, у вас все будет хорошо. При этом вы, вероятно, должны проверить, не используется ли distutils в вашем коде, и начать готовиться к тому, чтобы в ближайшее время избавиться от этого модуля.

Синтаксис менеджера контекста

Контекстные менеджеры отлично подходят, чтобы открывать и закрывать файлы, работать с соединениями баз данных и делать многое другое, а в Python 3.10 они станут немного удобнее. Изменение позволяет в скобках указывать несколько контекстных менеджеров, что удобно, если вы хотите создать в одном операторе with несколько менеджеров:

with (
    open("somefile.txt") as some_file,
    open("otherfile.txt") as other_file,
):
    ...

from contextlib import redirect_stdout

with (open("somefile.txt", "w") as some_file,
      redirect_stdout(some_file)):
    ...

В коде выше видно, что мы даже можем ссылаться на переменную, созданную одним контекстным менеджером (... as some_file) в следующем за ним менеджере!

Это всего лишь два из многих новых форматов в Python 3.10. Улучшенный синтаксис довольно гибок, поэтому я не буду утруждать себя и показать все возможные варианты; я почти уверен, что новый Python обработает всё, что вы ему скормите.

Улучшения в производительности

Как и во всех последних релизах Python, с Python 3.10 придут улучшения производительности. Первое — оптимизация конструкторов str(), bytes() и bytearray(), которые должны стать примерно на 30% быстрее (фрагмент, адаптированный из примера в баг-трекере Python):

~ $ ./python3.10 -m pyperf timeit -q --compare-to=python "str()"
Mean +- std dev: [python] 81.9 ns +- 4.5 ns -> [python3.10] 60.0 ns +- 1.9 ns: 1.36x faster (-27%)
~ $ ./python3.10 -m pyperf timeit -q --compare-to=python "bytes()"
Mean +- std dev: [python] 85.1 ns +- 2.2 ns -> [python3.10] 60.2 ns +- 2.3 ns: 1.41x faster (-29%)
~ $ ./python3.10 -m pyperf timeit -q --compare-to=python "bytearray()"
Mean +- std dev: [python] 93.5 ns +- 2.1 ns -> [python3.10] 73.1 ns +- 1.8 ns: 1.28x faster (-22%)

Другой более заметной оптимизацией (если вы используете аннотации типов) является то, что параметры функции и их аннотации вычисляются уже не во время исполнения, а во время компиляции. Теперь функция с аннотациями параметров создаётся примерно в два раза быстрее.

Кроме того, есть еще несколько оптимизаций в разных частях ядра языка. Подробности о них вы можете найти в этих записях баг-трекера Python: bpo-41718, bpo-42927 и bpo-43452.

Сопоставление шаблонов

Одна масштабная фича, о которой вы, конечно, слышали, — это структурное сопоставление шаблонов, добавляющее оператор известное выражение case из других языков. Мы знаем, как работать с case, но посмотрите на вариацию в Python это не просто switch/case, но также несколько мощных особенностей, которые мы должны исследовать.

Простое сопоставление шаблонов состоит из ключевого слова match, за которым следует выражение, а его результат проверяется на соответствие шаблонам, указанным в последовательных операторах case:

def func(day):
    match day:
        case "Monday":
            return "Here we go again..."
        case "Friday":
            return "Happy Friday!"
        case "Saturday" | "Sunday":  # Multiple literals can be combined with `|`
            return "Yay, weekend!"
        case _:
            return "Just another day..."

В этом простом примере мы воспользовались переменной day как выражением, которое затем сравнивается с конкретными строками в case. Кроме строк, вы также можете заметить case с маской _ — это эквивалент ключевого слова default в других языках. Хотя этот оператор можно опустить, в этом случае может произойти no-op, по существу это означает, что вернётся None.

Еще один момент, на который стоит обратить внимание в коде выше, это оператор |, позволяющий комбинировать несколько литералов | (другой его вариант — or).

Как я уже упоминал, новое сопоставление шаблонов не заканчивается на базовом синтаксисе, напротив — оно привносит дополнительные возможности, например сопоставление сложных шаблонов:

def func(person):  # person = (name, age, gender)
    match person:
        case (name, _, "male"):
            print(f"{name} is man.")
        case (name, _, "female"):
            print(f"{name} is woman.")
        case (name, age, gender):
            print(f"{name} is {age} old.")
        
func(("John", 25, "male"))
# John is man.

Во фрагменте выше мы воспользовались кортежем как выражением сопоставления. Однако мы не ограничены кортежами: работать будет любой итерируемый тип. Также выше видно, что маска (wildcard) _ может применяться внутри сложных шаблонов и не только сама по себе, как в предыдущих примерах. Простые кортежи или списки — не всегда лучший подход, поэтому, если вы предпочитаете классы, код можно переписать так:

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int
    gender: str
    
def func(person):  # person is instance of `Person` class
    match person:
        # This is not a constructor
        case Person(name, age, gender) if age < 18:  # guard for extra filtering
            print(f"{name} is a child.")
        case Person(name=name, age=_, gender="male"):  # Wildcard ("throwaway" variable) can be used
            print(f"{name} is man.")
        case Person(name=name, age=_, gender="female"):
            print(f"{name} is woman.")
        case Person(name, age, gender):  # Positional arguments work
            print(f"{name} is {age} years old.")

func(Person("Lucy", 30, "female"))
# Lucy is woman.
func(Person("Ben", 15, "male"))
# Ben is a child.

Здесь видно, что с шаблонами, написанными в стиле конструкторов, можно сопоставить атрибуты класса. При использовании этого подхода отдельные атрибуты также попадают в переменные (как и в показанные ранее кортежи), с которыми затем можно работать в соответствующем операторе case.

Выше мы можем увидеть другие особенности сопоставления шаблонов: во-первых выражение в case — это гард, который также является условием в if. Это полезно, когда сопоставления по значению не достаточно и вам нужны дополнительные проверки. Посмотрите на оставшееся выражение case: видно, что и ключевые слова, (name-name) и позиционные аргументы работают с синтаксисом, похожим на синтаксис конструкторов; то же самое верно для маски _ (или отбрасываемой переменной).

Сопоставление шаблонов также позволяет работать с вложенными шаблонами. Вложенные шаблоны могут использовать любой итерируемый тип: и конструируемый объект, и несколько таких объектов, которые возможно итерировать:

match users:
    case [Person(...)]:
        print("One user provided...")
    case [Person(...), Person(...) as second]:  # `as var` can be used to capture subpattern
        print(f"There's another user: {second}")
    case [Person(...), Person(...), *rest]:  # `*var` can be used as unpacking
        print(...)

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

Наконец, оператор * может использоваться для "распаковки" переменных в шаблоне, это работает и с маской _ в шаблоне *_. Если вы хотите увидеть больше примеров и законченный туториал, пройдите по ссылке на PEP 636.

Заключение

В Python 3.10 появилось много интересных новых возможностей, но этот релиз — alpha (вскоре появится beta) всё ещё далек от полноценного тестирования и готовности к производственной среде. Поэтому определенно не стоит начинать использовать его.

Наверное, лучше подождать полноценного релиза в октябре и, возможно, время от времени проверять страницу Что нового в Python 3.10, чтобы увидеть изменения, которые могут появиться на последних минутах.

С учётом сказанного выше - если вы хотите обновиться - неплохой идеей может быть взять на тест первый релиз beta (который будет в июне), чтобы посмотреть, совместима ли ваша кодовая база со всеми изменениями в новой версии, включая устаревание и удаление модулей и функций.

А если хотите прокачать себя в Python — приходите учиться, например на направление Fullstack-разработчик, где мы делаем универсальных специалистов, которые высоко ценятся на рынке.

Узнайте, как прокачаться в других специальностях или освоить их с нуля:

Другие профессии и курсы
SkillFactory
Школа Computer Science. Скидка 10% по коду HABR

Комментарии 54

    +10
    Это, безусловно, хорошо, но давайте будем реалистами: реализация этой функции не так уж сложна, на самом деле это всего лишь одна строка:
    def bit_count(value): return bin(value).count("1")

    Ну, одно дело перевод в строку, а потом подсчёт единиц, другое — bit_count.

      +1
      Я видал в продакшн-коде, как htonl() был реализован через hex(), строковые манипуляции, и int(,16) :-/
        +2

        Как насчёт сравнения числа с нулем через перевод в строку и проверку, является ли первый символ минусом?


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

        0
        int.bit_count (если верить официальной документации) считает именно количество единиц:
        The int type has a new method int.bit_count(), returning the number of ones in the binary expansion of a given integer, also known as the population count. (Contributed by Niklas Fiekas in bpo-29882.)
          +1

          Я косо выразился: эффективность этого метода, перегоном через строку — крайне мала.


          Как заметил наш коллега, есть куда более эффективный способ считать единицы в int'е, пусть и для Python этот вариант придется адаптировать из-за поддержки больших чисел.

            0
            Эффективный, но непереносимый :)

            Фактически используемый код:
            u -= (u >> 1) & 0x55555555;
            u = (u & 0x33333333) + ((u >> 2) & 0x33333333);
            u = (u + (u >> 4)) & 0x0f0f0f0f;
            return (u * 0x01010101) >> 24;


            Сумеет ли компилятор свернуть это в одну инструкцию процессора — зависит и от компилятора, и от процессора.
              0

              Всегда к этому можно добавить необходимые оптимизации в виде (я не пишу на си/плюсах, точных объявлений макросов не знаю):


              #if MSVC
              #ifdef _M_X86
              ...
              #endif
              #endif
              
              #if GCC
              #ifdef __i386__
              ...
              #endif
              #endif
        +1
        Сделать это можно выполнив эти команды:
        А лучше воспользоваться pyenv.

        ЗЫ. Паттерн матчинг из Scala, yay!!!
          +9
          bitcount реализован через превращение числа в строку и подсчет ASCII-кодов символа '1'??? Ничего себе, вообще-то есть ассемблерная команда такая, popcnt:)
            +2
            Всетаки в репозиторий добавили функцию на С, а пример с преобразованим в строку — это то как это можно сделать на чистом питоне.
              +1

              А что, Питон только на x86 работает? (риторический вопрос)


              В GCC, Clang есть __builtin_popcount(), который транслируется уже в то, что будет самым эффективным на данной платформе с данными опциями сборки. Если же другой компилятор, то есть масса приёмов типа такого, которые решают задачу уже независимо от возможностей процессора. (В конкретном CPython этот builtin не применили, видимо, из-за нежелания завязываться на компилятор. Для супероптимизированного кода это было бы проблемой, но для Python с его 20-100 раз потерей на динамической типизации и т.п. — точно не страшно).

              +1
              Здесь видно, что с шаблонами, написанными в стиле конструкторов, можно сопоставить атрибуты класса.

              по-моему, синтаксис не очень получился. Вносит сумятицу, т.к. там как будто бы конструктор, а значит предполагается потом сравнение ссылок на экземпляры. Что не должно срабатывать, а срабатывает, потому что это «не конструктор, но выглядит как конструктор».
                +1

                Еще не очень понятно, а что будет, если вместо строкового литерала вписать переменную с данным значением? Скорее всего — нельзя? Тот же вопрос про переменную в "конструкторе", которой присвоится значение из аргумента match. Она не должна быть ранее определена?

                +4
                Отчаянные питонисты всё еще ждут появления многопоточности с использованием всех ядер, чтоб уже наконец-то начать использовать потоки по полной…
                  +10

                  Для тех, кто прочитал больше, чем учебник "Выучи Питон за неделю", вопрос давно решён и закрыт.

                    –3
                    поэтому я и написал отчаянные питонисты
                      +3
                      Чтобы высказать экспертное мнение учить язык совершенно необязательно (тем более аж целую неделю).
                      Досточно погуглить.
                      PS. s/язык/предмет экспертизы/g; s/тем более аж целую неделю/вообще/g
                        0
                        а где прочитать про многопоточность в питоне?
                          +2

                          Есть несколько способов, у каждого свои плюсы и минусы.


                          • В стандартной библиотеке есть модули multithreading и multiprocessing
                          • Numba умеет создавать многопоточность для своих функций, это очень круто при обработке массивов данных
                          • Наконец, есть альтернативы интерпретатору CPython. У альтернатив нет проблем с многопоточностью, там multithreading работает именно так, как ожидаешь. Только в целом они плохо развиваются.
                            0
                            >multithreading
                            Невижу в стандартной. threading не умеет в мультипоток, там все так же лочится, это скорее асинхронка.
                            >multiprocessing
                            Это не мультипоток.

                            Numba это вообще не в тему, это ближе к векторным вычислениям.

                              +1

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


                              multiprocessing это развитие на тему Линуксного fork(). Когда-то ничего другого и не было. Да и сейчас нередко используют fork() в С.


                              Numba почему не в тему? Он делает полноценные потоки. Да, вызов в формате map pool, но всегда можно подать на вход range() и не читать его.

                        0

                        После того, как я поигрался с многопоточностью в Rust'е (даже в rust'е!) мой энтузиазм сильно поугас. За пределами rayon'а (job stealing) оно настолько выносит мозг (вне зависимости от языка программирования), что легче плюнуть, чем продираться.

                        –3
                        match вместо switch — видимо Oracle запатентовала слово switch.
                        Сделали бы как в других языках чтобы не было путаницы с регулярными выражениями.
                          +3

                          Сопоставление с образцом и оператор ветвления — вещи принципиально разные, хотя второе реализует подмножество поведений первого. В других языках для этого обычно используется как раз match (например, Rust, Nemerle).

                            –5

                            на самом деле слово switch оскорбительно
                            посмотрите сами
                            одно из значений — хлыст.


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


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

                              0
                              Баста, карапузики — времена, когда такие шутки действительно воспринимались шутками, прошли.
                              Своим сарказмом Вы оскорбили москhumorless-сообщество. Выгребайте.
                                +3

                                какой юмор.
                                я оскорблен тем, что кто-то посчитал моё предположение шуткой.
                                Ничего смешного в этом нет.
                                Более того, с болью в сердце я вынужден признаться: мои предки так же подвергались угнетению, угонялись в рабство и подвергались внеэкономической эксплуатации.


                                Да, да! Османское иго никто не забыл и не собирается просто так прощать.
                                Да и к венграм у меня есть серьезные вопросы по поводу кое-каких событий пятивековой давности.
                                Мысли об этом ранят мне душу и не дают спокойно кушать.
                                И вообще...


                                только для тех, кто может от 04.04 отнять три дня

                                image

                                  0
                                  Как определить, что партнёр имитирует сарказм?
                            +3

                            Написано терпимо для гуглоперевода, но как образовательному ресурсу вам следует знать, что pattern matching по-русски называется "сопоставление с образцом".

                              +1
                              Однако, начиная с Python 3.10, синтаксис определения псевдонимов типов будет изменён:

                              FileName: TypeAlias = str
                              Серьёзно? Добавление подсказки типа переменной (точнее, самого типа для этой подсказки) автор называет изменением синтаксиса?

                              def func(person):  # person = (name, age, gender)
                                  match person:
                                      case (name, _, "male"):
                                          print(f"{name} is man.")
                                      case (name, _, "female"):
                                          print(f"{name} is woman.")
                                      case (name, age, gender):
                                          print(f"{name} is {age} old.")
                                      
                              func(("John", 25, "male"))
                              # John is man.

                              Не понял, откуда здесь взялись переменные name и _
                                +2
                                Не понял, откуда здесь взялись переменные name и _

                                Из оператора match: он ей и присваивает значение.
                                  –1
                                  А я не понял, почему третий кейс не сработал…
                                    +2
                                    Потому что из всех кейсов срабатывает как максимум один.
                                      –1
                                      То есть если рассматривать не земных людей, а странных невообразимых существ, у которых существует ровно два пола (male и female), то вышеприведённый код никогда не будет выводить возраст?
                                        +5
                                        Ок, ёрничанье на тему пятидесяти полов (или сколько их там сейчас насчитывается), возможно, было и лишним. Но хотелось бы всё-таки по существу вопроса получить ответ. Если работать только с данными, где поле gender может принимать лишь одно из значений «male» или «female», то правильно ли я понял, что третья ветка case никогда не будет вызвана?
                                          0
                                          Да, совершенно верно.
                                    0

                                    Потому что проверка совпадений идет по порядку их объявления, оно попало под паттерн на первом кейсе.

                                  –3

                                  Короче, никаких изменений

                                    0
                                    Match одобрям, двумя руками)
                                      0

                                      Что-то питон стал напоминать Свифт :)

                                        –4

                                        Match невообразимо переусложнили. Нафига? Задача была — сделать простой switch, а родилось нечто, что будет непонятно 95 процентам читающих код. Я бы такой код в продакшн не пускал.

                                          +2
                                          Мне кажется, дело здесь в сумбурном изложении
                                          Никто не запрещает пользоваться «базовым» match, который просто позволит избавиться от длинных цепочек if-elif-else (в пользу столь же длинных цепочек match)
                                            0
                                            Проблемы возникнут, когда потребуется разбирать чужой код.
                                              +1
                                              Нет такого языка, который бы не позволял писать тяжелочитаемый код.
                                                –1
                                                Одно дело, когда программист пишет тяжелочитаемый код. Другое дело, когда сама синтаксическая конструкция языка оказывается сбивающей с толку и означающей совсем не то, что кажется на первый взгляд.
                                                0
                                                А с длиннющей цепочной if-elif-else не возникнут?
                                                  0
                                                  Речь не о длине, а о понятности кода. Этот «неконструкторный конструктор» очень нелогичен. Да, лично я в своём коде могу его и не использовать. Но когда буду читать чужой код, где эта конструкция используется, мне придётся постоянно помнить, что тут не инициализация объекта, а нечто совсем другое, хотя и выглядит абсолютно так же.
                                                    0
                                                    Python очень давно позволяет писать [a, [b, c]] = something(), и это не создание вложенных списков, а наоборот, их разворачивание. Эта конструкция вас тоже смущала?
                                                      0
                                                      Нет, не смущала, потому что я никогда не называл её для себя разворачиванием списков. Для меня это просто поэлементное присвоение одного набора объектов другому набору. Если бы это записывалось как SomeClass(a, b, c) = something(), то смущало бы.
                                              +1
                                              В 2021-ом году не было и не могло быть задачи «сделать простой switch» (на уровне K&R C примерно 40-летней давности). Это было бы немножко… неактуально, вы не находите ли?

                                              Всё-таки немножко актуальнее задача «сделать простой match» (на уровне Haskell 25-летней давности или Scala 17-летней).
                                                +1
                                                … или ML 48-летней.

                                                Не спорю, что это фича более новая и продвинутая, чем switch у K&R, но совсем ненамного — может на пару лет.
                                              +2
                                              Для тех. кому все-таки нужно поддерживать легаси код на продакшен версии CPython есть перевод документации по Python 3 на русском языке https://digitology.tech/docs/python_3.
                                                0
                                                «Написание модуля расширения является относительно хорошо понятным процессом, где хорошо работает подход «поваренной книги». Существует несколько инструментов, которые в некоторой степени автоматизируют процесс. Хотя люди встраивают Python в другие приложения с самого начала своего существования, процесс встраивания Python менее прост, чем написание расширения.»
                                                Там есть вот такое… хотя, может, потом и убрали.

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

                                              Самое читаемое