You are currently viewing Python 3.10: Новые интересные функции, которые Вы можете попробовать

Python 3.10: Новые интересные функции, которые Вы можете попробовать

Содержание

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

Каждая новая версия Python вносит множество изменений. Вы можете прочитать обо всех из них в документации. Здесь вы узнаете о самых крутых новых функциях.

В этом уроке вы узнаете:

  • Отладка с более полезными и точными сообщениями об ошибках
  • Использование сопоставления структурных шаблонов для работы со структурами данных
  • Добавление более удобочитаемых и более конкретных подсказок типа
  • Проверка длины последовательностей при использовании zip()
  • Вычисление многомерной статистики

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

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

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

Вспомните, как вы писали свою первую программу Hello World на Python:

# hello.py

print("Hello, World!)

Возможно, вы создали файл , добавили в него знаменитый вызов print()и сохранили его как hello.py. Затем вы запустили программу, стремясь назвать себя настоящим питонистом. Однако что-то пошло не так:

$ python hello.py
  File "/home/rp/hello.py", line 3
    print("Hello, World!)
                        ^
SyntaxError: EOL while scanning string literal

SyntaxError В коде было «а». EOL, что это вообще значит? Вы вернулись к своему коду и после небольшого изучения и поиска поняли, что в конце вашей строки отсутствует кавычка.

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

$ python hello.py
  File "/home/rp/hello.py", line 3
    print("Hello, World!)
          ^
SyntaxError: unterminated string literal (detected at line 3)

Сообщение об ошибке все еще немного техническое, но загадочное исчезло EOL. Вместо этого в сообщении говорится, что вам необходимо завершить свою строку! Существуют аналогичные улучшения для многих различных сообщений об ошибках, как вы увидите ниже.

A SyntaxError — это ошибка, возникающая при анализе вашего кода еще до того, как он начнет выполняться. Синтаксические ошибки могут быть сложными для отладки, поскольку интерпретатор выдает неточные или иногда даже вводящие в заблуждение сообщения об ошибках. В следующем коде отсутствует фигурная скобка для завершения словаря:

 # unterminated_dict.py
 
 months = {
     10: "October",
     11: "November",
     12: "December"
 
 print(f"{months[10]} is the tenth month")

Отсутствующая закрывающая фигурная скобка, которая должна была быть в строке 7, является ошибкой. Если вы запустите этот код с Python 3.9 или более ранней версии, вы увидите следующее сообщение об ошибке:

  File "/home/rp/unterminated_dict.py", line 8
    print(f"{months[10]} is the tenth month")
    ^
SyntaxError: invalid syntax

Сообщение об ошибке выделяет строку 8, но в строке 8 нет синтаксических проблем! Если вы сталкивались со своей долей синтаксических ошибок в Python, вы, возможно, уже знаете, что хитрость заключается в том, чтобы посмотреть на строки до того, как на них пожалуется Python. В этом случае вы ищете недостающую закрывающую скобу в строке 7.

В Python 3.10 тот же код показывает гораздо более полезное и точное сообщение об ошибке:

  File "/home/rp/unterminated_dict.py", line 3
    months = {
             ^
SyntaxError: '{' was never closed

Это указывает вам прямо на словарь-нарушитель и позволяет устранить проблему в кратчайшие сроки.

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

 # missing_comma.py
 
 months = {
     10: "October"
     11: "November",
     12: "December",
 }

В этом коде в конце строки 4 отсутствует запятая. Python 3.10 дает вам четкое представление о том, как исправить ваш код:

  File "/home/real_python/missing_comma.py", line 4
    10: "October"
        ^^^^^^^^^
SyntaxError: invalid syntax. Perhaps you forgot a comma?

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

Еще одной распространенной ошибкой является использование оператора присваивания (=) вместо оператора сравнения равенства (==) при сравнении значений. Раньше это просто вызывало бы другое invalid syntax сообщение. В новейшей версии Python вы получите еще несколько советов:>>>

>>> if month = "October":
  File "<stdin>", line 1
    if month = "October":
       ^^^^^^^^^^^^^^^^^
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?

Анализатор предполагает, что вы, возможно, хотели использовать вместо этого оператор сравнения или оператор выражения присваивания.

Обратите внимание на еще одно замечательное улучшение в сообщениях об ошибках Python 3.10. Последние два примера показывают, как каретки (^^^) выделяют все оскорбительное выражение. Ранее один символ курсора (^) указывал только приблизительное местоположение.

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

>>> import math
>>> math.py
AttributeError: module 'math' has no attribute 'py'. Did you mean: 'pi'?

>>> pint
NameError: name 'pint' is not defined. Did you mean: 'print'?

>>> release = "3.10"
>>> relaese
NameError: name 'relaese' is not defined. Did you mean: 'release'?

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

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

Соответствие структурному шаблону

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

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

  1. Обнаружение и деконструкция различных структур в ваших данных
  2. Использование различных видов шаблонов
  3. Соответствие буквальным шаблонам

Сопоставление структурных шаблонов-это комплексное дополнение к языку Python. Чтобы дать вам представление о том, как вы можете использовать его в своих собственных проектах, в следующих трех подразделах вы погрузитесь в некоторые детали. Вы также увидите некоторые ссылки, которые могут помочь вам изучить еще более подробно, если вы хотите.

Деконструкция структур данных

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

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

Время соответствовать вашему первому образцу! В следующем примере используется match ... case блок для поиска имени пользователя путем извлечения его из user структуры данных:>>>

>>> user = {
...     "name": {"first": "Pablo", "last": "Galindo Salgado"},
...     "title": "Python 3.10 release manager",
... }

>>> match user:
...     case {"name": {"first": first_name}}:
...         pass
...

>>> first_name
'Pablo'

Вы можете увидеть соответствие структурному шаблону в работе в выделенных строках. user представляет собой небольшой словарь с информацией о пользователе. case Линия указывает шаблон, с которым user сопоставляется. В этом случае вы ищете словарь с "name" ключом, значением которого является новый словарь. Этот вложенный словарь имеет ключ под названием "first". Соответствующее значение привязано к переменной first_name.

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

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

Вы можете развернуть свернутый раздел ниже, чтобы увидеть, как вы можете использовать requests для получения различных версий пользовательских данных с помощью API:

Получение Случайных Пользовательских ДанныхПоказать/Скрыть

В этом примере вы будете работать с информацией о дате рождения (dob) для каждого пользователя. Структура этих данных изменилась между различными версиями API случайного пользователя:

# Version 1.1
"dob": "1966-04-17 11:57:01"

# Version 1.3
"dob": {"date": "1957-05-20T08:36:09.083Z", "age": 64}

Обратите внимание, что в версии 1.1 дата рождения представлена в виде простой строки, в то время как в версии 1.3 это объект JSON с двумя членами: "date" и "age". Скажите, что вы хотите узнать возраст пользователя. В зависимости от структуры ваших данных вам нужно будет либо рассчитать возраст на основе даты рождения, либо посмотреть возраст, если он уже доступен.

Примечание: Значение age является точным при загрузке данных. Если вы сохраните данные, это значение в конечном итоге устареет. Если это вызывает беспокойство, вам следует рассчитать текущий возраст на основе date.

Традиционно вы бы определили структуру данных с if помощью теста, возможно, на основе типа "dob" поля. Вы можете подойти к этому по-другому в Python 3.10. Теперь вместо этого вы можете использовать сопоставление структурных шаблонов:

# random_user.py (continued)

from datetime import datetime

def get_age(user):
    """Get the age of a user"""
    match user:
        case {"dob": {"age": int(age)}}:
            return age
        case {"dob": dob}:
            now = datetime.now()
            dob_date = datetime.strptime(dob, "%Y-%m-%d %H:%M:%S")
            return now.year - dob_date.year

match ... case Конструкция является новой в Python 3.10 и представляет собой способ выполнения сопоставления структурных шаблонов. Вы начинаете с match инструкции, которая указывает, чему вы хотите соответствовать. В этом примере это структура user данных.

Далее следует одно или несколько case утверждений match. Каждый case описывает один шаблон, и блок с отступом под ним говорит, что должно произойти, если есть совпадение. В этом примере:

  • Строка 8 сопоставляет словарь с "dob" ключом, значением которого является другой словарь с int именем элемента integer ()"age". Название age отражает его значение.
  • Строка 10 соответствует любому словарю с "dob" ключом. Название dob отражает его значение.

Одной из важных особенностей сопоставления шаблонов является то, что будет сопоставлено не более одного шаблона. Поскольку шаблон в строке 10 соответствует любому словарю "dob", важно, чтобы на первом месте был более конкретный шаблон в строке 8.

Примечание: Расчет возраста, выполненный в строке 13, не очень точен, так как в нем игнорируются даты. Вы можете улучшить это, явно сравнивая месяцы и дни, чтобы проверить, отпраздновал ли пользователь свой день рождения в этом году. Однако лучшим решением было бы использовать relativedelta пакет dateutil. С помощью relativedelta этого вы можете напрямую рассчитать годы.

Прежде чем более подробно изучить детали шаблонов и то, как они работают, попробуйте позвонить get_age() с помощью различных структур данных, чтобы увидеть результат:>>>

>>> import random_user

>>> users11 = random_user.get_user(version="1.1")
>>> random_user.get_age(users11)
55

>>> users13 = random_user.get_user(version="1.3")
>>> random_user.get_age(users13)
64

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

Посмотрите внимательнее на эти узоры. Первый шаблон, {"dob": {"age": int(age)}}, соответствует версии 1.3 пользовательских данных:

{
    ...
    "dob": {"date": "1957-05-20T08:36:09.083Z", "age": 64},
    ...
}

Первый шаблон-это вложенный шаблон. Во внешних фигурных скобках указано, что требуется словарь с ключом"dob". Соответствующее значение должно быть словарем. Этот вложенный словарь должен соответствовать подшаблону {"age": int(age)}. Другими словами, у него должен быть "age" ключ с целочисленным значением. Это значение привязано к имени age.

Второй шаблон, {"dob": dob}, соответствует более старой версии 1.1 пользовательских данных:

{
    ...
    "dob": "1966-04-17 11:57:01",
    ...
}

Этот второй шаблон является более простым шаблоном, чем первый. Опять же, фигурные скобки указывают на то, что он будет соответствовать словарю. Однако любой словарь с "dob" ключом сопоставляется, поскольку другие ограничения не указаны. Значение этого ключа привязано к имени dob.

Главный вывод заключается в том, что вы можете описать структуру своих данных, используя в основном знакомые обозначения. Однако одно поразительное изменение заключается в том, что вы можете использовать такие имена, как dob и age, которые еще не определены. Вместо этого значения из ваших данных привязываются к этим именам, когда шаблон совпадает.

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

Использование различных видов шаблонов

Вы видели пример того, как можно использовать шаблоны для эффективного распутывания сложных структур данных. Теперь вы сделаете шаг назад и посмотрите на строительные блоки, из которых состоит эта новая функция. Многие вещи объединяются, чтобы заставить его работать. На самом деле, существует три предложения по улучшению Python (PEP), которые описывают соответствие структурным шаблонам:

  1. PEP 634: Спецификация
  2. ОПТОСОЗ 635: Мотивация и обоснование
  3. PEP 636: Учебное пособие

Эти документы дают вам много информации и подробностей, если вы заинтересованы в более глубоком погружении, чем то, что следует ниже.

Шаблоны находятся в центре структурного сопоставления шаблонов. В этом разделе вы узнаете о некоторых различных типах существующих шаблонов:

  • Шаблоны отображения соответствуют структурам отображения, таким как словари.
  • Шаблоны последовательностей соответствуют структурам последовательностей, таким как кортежи и списки.
  • Шаблоны захвата привязывают значения к именам.
  • ПОСКОЛЬКУ шаблоны привязывают значения подшаблонов к именам.
  • ИЛИ шаблоны соответствуют одному из нескольких различных подшаблонов.
  • Шаблоны подстановочных знаков соответствуют чему угодно.
  • Шаблоны классов соответствуют структурам классов.
  • Шаблоны значений соответствуют значениям, хранящимся в атрибутах.
  • Буквальные шаблоны соответствуют буквальным значениям.

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

Шаблон захвата используется для захвата соответствия шаблону и привязки его к имени. Рассмотрим следующую рекурсивную функцию, которая суммирует список чисел:

 def sum_list(numbers):
     match numbers:
         case []:
             return 0
         case [first, *rest]:
             return first + sum_list(rest)

Первая case строка 3 соответствует пустому списку и возвращается 0 в виде его суммы. Во второй case строке 5 используется шаблон последовательности с двумя шаблонами захвата для сопоставления списков с одним или несколькими элементами. Первый элемент в списке захватывается и привязывается к имени first. Второй шаблон захвата, *rest, использует синтаксис распаковки для сопоставления любого количества элементов. rest будет привязан к списку, содержащему все элементы numbers, кроме первого.

sum_list() вычисляет сумму списка чисел путем рекурсивного добавления первого числа в списке и суммы остальных чисел. Вы можете использовать его следующим образом:>>>

>>> sum_list([4, 5, 9, 4])
22

Сумма 4 + 5 + 9 + 4 правильно рассчитано, чтобы быть 22. В качестве упражнения для себя вы можете попытаться отследить рекурсивные вызовы sum_list(), чтобы убедиться, что вы понимаете, как код суммирует весь список.

Примечание. Шаблоны захвата по существу присваивают значения переменным. Однако одно ограничение заключается в том, что допускаются только имена без кавычек. Другими словами, вы не можете использовать шаблон захвата для прямого назначения атрибута класса или экземпляра.

sum_list() обрабатывает суммирование списка чисел. Понаблюдайте, что произойдет, если вы попытаетесь суммировать все, что не является списком:>>>

>>> print(sum_list("4594"))
None

>>> print(sum_list(4594))
None

Передача строки или числа для sum_list() возврата None. Это происходит потому, что ни один из шаблонов не совпадает, и выполнение продолжается после match блока. Это, оказывается, конец функции, поэтому sum_list() неявно возвращает None.

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

def sum_list(numbers):
    match numbers:
        case []:
            return 0
        case [first, *rest]:
            return first + sum_list(rest)
        case _:
            wrong_type = numbers.__class__.__name__
            raise ValueError(f"Can only sum lists, not {wrong_type!r}")

Финал case будет соответствовать всему, что не соответствует первым двум шаблонам. Это приведет к описательной ошибке, например, если вы попытаетесь вычислить sum_list(4594). Это полезно, когда вам нужно предупредить пользователей о том, что некоторые входные данные не были сопоставлены должным образом.

Однако ваши модели все еще не являются надежными. Подумайте, что произойдет, если вы попытаетесь суммировать список строк:>>>

>>> sum_list(["45", "94"])
TypeError: can only concatenate str (not "int") to str

Возвращается базовый регистр 0, поэтому суммирование работает только для типов, которые вы можете добавить с помощью чисел. Python не знает, как складывать числа и текстовые строки вместе. Вы можете ограничить свой шаблон только целыми числами, используя шаблон класса:

def sum_list(numbers):
    match numbers:
        case []:
            return 0
        case [int(first), *rest]:
            return first + sum_list(rest)
        case _:
            raise ValueError(f"Can only sum lists of numbers")

При добавлении int()first убедитесь, что шаблон совпадает только в том случае, если значение является целым числом. Однако это может быть слишком ограничительным. Ваша функция должна уметь суммировать как целые числа, так и числа с плавающей запятой, так как вы можете разрешить это в своем шаблоне?

Чтобы проверить, соответствует ли хотя бы один из нескольких подшаблонов, можно использовать шаблон ИЛИ. ИЛИ шаблоны состоят из двух или более подшаблонов, и шаблон совпадает, если хотя бы один из подшаблонов совпадает. Вы можете использовать это для сопоставления, когда первый элемент имеет int тип или тип float:

def sum_list(numbers):
    match numbers:
        case []:
            return 0
        case [int(first) | float(first), *rest]:
            return first + sum_list(rest)
        case _:
            raise ValueError(f"Can only sum lists of numbers")

Вы используете символ трубы (|) для разделения подшаблонов в шаблоне ИЛИ. Ваша функция теперь позволяет суммировать список чисел с плавающей запятой:>>>

>>> sum_list([45.94, 46.17, 46.72])
138.82999999999998

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

Если вам интересно, ознакомьтесь с документацией, чтобы узнать больше об этих функциях. В следующем разделе вы узнаете о буквальных шаблонах и шаблонах значений.

Соответствие буквальным шаблонам

Буквальный шаблон-это шаблон, который соответствует буквальному объекту, такому как явная строка или число. В некотором смысле это самый базовый тип шаблона, который позволяет вам эмулировать switch ... case операторы, используемые в других языках. Следующий пример соответствует определенному имени:

def greet(name):
    match name:
        case "Guido":
            print("Hi, Guido!")
        case _:
            print("Howdy, stranger!")

Первый case соответствует буквальной строке "Guido". В этом случае вы используете _подстановочный знак для печати общего приветствия, когда name это не "Guido" так . Такие буквальные шаблоны иногда могут заменять if ... elif ... else конструкции и могут играть ту же роль, что switch ... case и в некоторых других языках.

Одним из ограничений при сопоставлении структурных шаблонов является то, что вы не можете напрямую сопоставлять значения, хранящиеся в переменных. Скажите, что вы определились bdfl = "Guido". Такой шаблон case bdfl:не будет совпадать "Guido". Вместо этого это будет интерпретироваться как шаблон захвата, который соответствует чему угодно и связывает это значение bdfl, эффективно перезаписывая старое значение.

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

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

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

import enum

class Pythonista(str, enum.Enum):
    BDFL = "Guido"
    FLUFL = "Barry"

def greet(name):
    match name:
        case Pythonista.BDFL:
            print("Hi, Guido!")
        case _:
            print("Howdy, stranger!")

В первом случае теперь используется шаблон значений для сопоставления Pythonista.BDFL, который является "Guido". Обратите внимание, что в шаблоне значений можно использовать любое пунктирное имя. Вы могли бы, например, использовать обычный класс или модуль вместо перечисления.

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

  • Вы заменяете числа, кратные 3, на шипение.
  • Вы заменяете числа, кратные 5, на buzz.
  • Вы заменяете числа, делящиеся как на 3, так и на 5, на fizzbuzz.

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

Типичное решение в Python будет использовать if ... elif ... else следующее:

def fizzbuzz(number):
    mod_3 = number % 3
    mod_5 = number % 5

    if mod_3 == 0 and mod_5 == 0:
        return "fizzbuzz"
    elif mod_3 == 0:
        return "fizz"
    elif mod_5 == 0:
        return "buzz"
    else:
        return str(number)

% Оператор вычисляет модуль, который вы можете использовать для проверки делимости. А именно, если модуль b равен 0 для двух чисел a и b, то a делится на b.

В fizzbuzz(), вы вычисляете number % 3 и number % 5, которое затем используете для проверки на делимость с 3 и 5. Обратите внимание, что сначала вы должны выполнить тест на делимость как с 3, так и с 5. Если нет, то числа, которые делятся как на 3, так и на 5, будут охватываться либо "fizz" случаями, либо "buzz"случаями.

Вы можете проверить, что ваша реализация дает ожидаемый результат:>>>

>>> fizzbuzz(3)
fizz

>>> fizzbuzz(14)
14

>>> fizzbuzz(15)
fizzbuzz

>>> fizzbuzz(92)
92

>>> fizzbuzz(65)
buzz

Вы можете сами убедиться, что 3 делится на 3, 65 делится на 5, а 15 делится как на 3, так и на 5, в то время как 14 и 92 не делятся ни на 3, ни на 5.

if ... elif ... else Структуру, в которой вы несколько раз сравниваете одну или несколько переменных, довольно просто переписать, используя вместо этого сопоставление шаблонов. Например, вы можете сделать следующее:

def fizzbuzz(number):
    mod_3 = number % 3
    mod_5 = number % 5

    match (mod_3, mod_5):
        case (0, 0):
            return "fizzbuzz"
        case (0, _):
            return "fizz"
        case (_, 0):
            return "buzz"
        case _:
            return str(number)

Вы подходите по обоим mod_3 и mod_5case Затем каждый шаблон соответствует либо буквенному номеру 0, либо подстановочному _знаку для соответствующих значений.

Сравните и сравните эту версию с предыдущей. Обратите внимание , как шаблон (0, 0) соответствует тесту mod_3 == 0 and mod_5 == 0, в то время (0, _) как соответствует mod_3 == 0.

Как вы видели ранее, вы можете использовать шаблон ИЛИ для сопоставления нескольких различных шаблонов. Например, поскольку mod_3 можно принимать только значения 01, и 2, вы можете заменить case (_, 0) на case (1, 0) | (2, 0). Помните, что (0, 0) это уже было рассмотрено.

Примечание: Если вы использовали switch ... case другие языки, вам следует помнить, что в сопоставлении шаблонов Python нет ошибок. Это означает, что когда case-либо будет выполнено не более одного, а именно первого case подходящего. Это отличается от таких языков, как C и Java. Вы можете справиться с большинством последствий ошибочных решений с помощью шаблонов ИЛИ.

Разработчики ядра Python сознательно решили не включать switch ... case операторы в язык ранее. Однако есть некоторые сторонние пакеты, которые это делают, например switchlang, который добавляет switch команду, которая также работает в более ранних версиях Python.

Объединения типов, псевдонимы и охранники

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

  1. PEP 604: Разрешить написание типов союзов как X | Y
  2. PEP 613: Явные Псевдонимы Типов
  3. PEP 647: Защита определяемого пользователем типа
  4. PEP 612: Переменные спецификации параметров

PEP 604, вероятно, будет наиболее широко использоваться из этих изменений в будущем, но вы получите краткий обзор каждой из функций в этом разделе.

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

from typing import List, Union

def mean(numbers: List[Union[float, int]]) -> float:
    return sum(numbers) / len(numbers)

Аннотация List[Union[float, int]] означает, что numbers это должен быть список, в котором каждый элемент является либо числом с плавающей запятой, либо целым числом. Это хорошо работает, но обозначения немного многословны. Кроме того, вам нужно импортировать как List и Union из typing.

Примечание: Реализация mean() выглядит простой, но на самом деле есть несколько угловых случаев, когда она потерпит неудачу. Если вам нужно рассчитать средства, вы должны использовать statistics.mean() вместо этого.

В Python 3.10 вы можете Union[float, int] заменить его более кратким float | int. Объедините это с возможностью использовать list вместо typing.List подсказок по типу, которые появились в Python 3.9. Затем вы можете упростить свой код, сохранив всю информацию о типе:

def mean(numbers: list[float | int]) -> float:
    return sum(numbers) / len(numbers)

Аннотацию numbersтеперь легче читать, и в качестве дополнительного бонуса вам не нужно было ничего импортировать из typing.

Особый случай типов объединения-это когда переменная может иметь либо определенный тип, либо быть None. Вы можете аннотировать такие необязательные типы либо как Union[None, T], либо, что эквивалентно, Optional[T] для какого-либо типа T.  Для необязательных типов нет нового специального синтаксиса, но вы можете использовать новый синтаксис объединения, чтобы избежать импорта typing.Optional:

address: str | None

В этом примере addressдопускается либо Noneили строка.

Вы также можете использовать новый синтаксис объединения во время выполнения в isinstance() или issubclass() тестах:>>>

>>> isinstance("mypy", str | int)
True

>>> issubclass(str, int | float | bytes)
False

Традиционно вы использовали кортежи для тестирования сразу нескольких типов—например, (str, int) вместо str | int. Этот старый синтаксис все еще будет работать.

Псевдонимы типов позволяют быстро определять новые псевдонимы, которые могут использоваться для более сложных объявлений типов. Например, предположим, что вы представляете игральную карту, используя кортеж строк масти и ранга, а также колоду карт по списку таких кортежей игральных карт. Затем вводится колода карт типа «как». list[tuple[str, str]]

Чтобы упростить аннотацию типов, вы определяете псевдонимы типов следующим образом:

Card = tuple[str, str]
Deck = list[Card]

Обычно это работает нормально. Однако средство проверки типов часто не может определить, является ли такое утверждение псевдонимом типа или просто определением обычной глобальной переменной. Чтобы помочь проверке типов—или действительно помочь вам в проверке типов—теперь вы можете явно аннотировать псевдонимы типов:

from typing import TypeAlias

Card: TypeAlias = tuple[str, str]
Deck: TypeAlias = list[Card]

Добавление TypeAlias аннотации проясняет намерение как для средства проверки типов, так и для всех, кто читает ваш код.

Защита типов используется для сужения типов соединений. Следующая функция принимает либо строку, либо None, но всегда возвращает кортеж строк, представляющих игральную карту:

def get_ace(suit: str | None) -> tuple[str, str]:
    if suit is None:
        suit = "♠"
    return (suit, "A")

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

В настоящее время средства проверки типов могут использовать только несколько различных конструкций, чтобы таким образом сузить типы объединения. С помощью нового typing.TypeGuard вы можете аннотировать пользовательские функции , которые можно использовать для сужения типов объединений:

from typing import Any, TypeAlias, TypeGuard

Card: TypeAlias = tuple[str, str]
Deck: TypeAlias = list[Card]

def is_deck_of_cards(obj: Any) -> TypeGuard[Deck]:
    # Return True if obj is a deck of cards, otherwise False

is_deck_of_cards() должен возвращать True или Falseв зависимости от того obj, представляет Deck ли объект или нет. Затем вы можете использовать функцию защиты, и средство проверки типов сможет правильно сузить типы:

def get_score(card_or_deck: Card | Deck) -> int:
    if is_deck_of_cards(card_or_deck):
        # Calculate score of a deck of cards
    ...

Внутри if блока средство проверки типов знает, что card_or_deck на самом деле это тип Deck. См. PEP 647 для получения более подробной информации.

Последняя новая функция ввода-это переменные спецификации параметров, которые связаны с переменными типа. Рассмотрим определение декоратора. В общем, это выглядит примерно так:

import functools
from typing import Any, Callable, TypeVar

R = TypeVar("R")

def decorator(func: Callable[..., R]) -> Callable[..., R]:
    @functools.wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> R:
        ...
    return wrapper

Аннотации означают, что функция , возвращаемая декоратором, является вызываемой с некоторыми параметрами и тем же типом возвращаемого R значения, что и функция, переданная в декоратор. Многоточие (...) в заголовке функции корректно допускает любое количество параметров, и каждый из этих параметров может быть любого типа. Однако нет никакой проверки того, что возвращаемый вызываемый объект имеет те же параметры, что и переданная функция. На практике это означает, что средства проверки типов не могут должным образом проверять оформленные функции.

К сожалению, вы не можете использовать TypeVar параметры, потому что не знаете, сколько параметров будет у функции. В Python 3.10 у вас будет доступ к ParamSpec тому, чтобы правильно вводить подсказки для этих типов вызываемых объектов. ParamSpec работает аналогично TypeVar, но работает сразу по нескольким параметрам. Вы можете переписать своего декоратора следующим образом, чтобы воспользоваться ParamSpec:

import functools
from typing import Callable, ParamSpec, TypeVar

P = ParamSpec("P")
R = TypeVar("R")

def decorator(func: Callable[P, R]) -> Callable[P, R]:
    @functools.wraps(func)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        ...
    return wrapper

Обратите внимание, что вы также используете P это при аннотировании wrapper(). Вы также можете использовать новое typing.Concatenate для добавления типов ParamSpecПодробные сведения и примеры см. в документации и PEP 612.

Более строгое сжатие последовательностей

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

В качестве примера рассмотрим следующую таблицу наборов Lego:

ИмяУстановить НомерКуски
Лувр21024695
Косой переулок759785544
НАСА Аполлон Сатурн V921761969
Тысячелетний сокол751927541
Нью-Йорк21028598

Одним из способов представления этих данных в простом Python было бы представление каждого столбца в виде списка. Это может выглядеть примерно так:>>>

>>> names = ["Louvre", "Diagon Alley", "Saturn V", "Millennium Falcon", "NYC"]
>>> set_numbers = ["21024", "75978", "92176", "75192", "21028"]
>>> num_pieces = [695, 5544, 1969, 7541, 598]

Обратите внимание, что у вас есть три независимых списка, но между их элементами существует неявное соответствие. Первое имя ("Louvre"), номер первого набора ("21024") и первое количество деталей (695) — все это описывает первый набор Lego.

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

zip() может использоваться для параллельного перебора этих трех списков:>>>

>>> for name, num, pieces in zip(names, set_numbers, num_pieces):
...     print(f"{name} ({num}): {pieces} pieces")
...
Louvre (21024): 695 pieces
Diagon Alley (75978): 5544 pieces
Saturn V (92176): 1969 pieces
Millennium Falcon (75192): 7541 pieces
NYC (21028): 598 pieces

Обратите внимание, как каждая строка собирает информацию из всех трех списков и отображает информацию об одном конкретном наборе. Это очень распространенный шаблон, который используется во множестве различных кодов Python, в том числе в стандартной библиотеке.

Вы также можете добавить list(), чтобы собрать содержимое всех трех списков в один вложенный список кортежей:>>>

>>> list(zip(names, set_numbers, num_pieces))
[('Louvre', '21024', 695),
 ('Diagon Alley', '75978', 5544),
 ('Saturn V', '92176', 1969),
 ('Millennium Falcon', '75192', 7541),
 ('NYC', '21028', 598)]

Обратите внимание, как вложенный список очень похож на исходную таблицу.

Темная сторона использования zip() заключается в том, что довольно легко ввести тонкую ошибку, которую может быть трудно обнаружить. Обратите внимание, что произойдет, если в одном из ваших списков отсутствует элемент:>>>

>>> set_numbers = ["21024", "75978", "75192", "21028"]  # Saturn V missing

>>> list(zip(names, set_numbers, num_pieces))
[('Louvre', '21024', 695),
 ('Diagon Alley', '75978', 5544),
 ('Saturn V', '75192', 1969),
 ('Millennium Falcon', '21028', 7541)]

Вся информация о съемках в Нью — Йорке исчезла! Кроме того, установленные номера для Saturn V и Millennium Falcon неверны. Если ваши наборы данных больше, такие ошибки может быть очень трудно обнаружить. И даже когда вы замечаете, что что-то не так, это не всегда легко диагностировать и исправить.

Проблема в том, что вы предположили, что три списка содержат одинаковое количество элементов и что информация в каждом списке находится в одном и том же порядке. После set_numbers повреждения это предположение больше не соответствует действительности.

PEP 618 вводит новый strict параметр ключевого zip() слова, который можно использовать для подтверждения того, что все последовательности имеют одинаковую длину. В вашем примере это вызовет ошибку, предупреждающую вас о поврежденном списке:>>>

>>> list(zip(names, set_numbers, num_pieces, strict=True))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: zip() argument 2 is shorter than argument 1

Когда итерация достигает набора Lego в Нью-Йорке, второй аргумент set_numbers уже исчерпан, в то время как в первом аргументе все еще остаются элементы names. Вместо того, чтобы молча выдавать неправильный результат, ваш код завершается ошибкой, и вы можете предпринять действия, чтобы найти и исправить ошибку.

Существуют случаи использования, когда вы хотите объединить последовательности неодинаковой длины. Разверните поле ниже, чтобы увидеть, как zip() и itertools.zip_longest()как с ними обращаться:

Боковая панель: Последовательности молнии неравной длиныПоказывают/Скрывают

Хотя strict на самом деле это не добавляет никаких новых функций zip(), это может помочь вам избежать этих труднодоступных ошибок.

Новые функции в statistics модуле

statistics Модуль был добавлен в стандартную библиотеку еще в 2014 году с выпуском Python 3.4. Цель statistics состоит в том, чтобы производить статистические вычисления на уровне графических калькуляторов, доступных в Python.

Примечание: statistics не предназначен для предоставления специальных числовых типов данных или полнофункционального статистического моделирования. Если стандартная библиотека не удовлетворяет вашим потребностям, взгляните на сторонние пакеты, такие как NumPy, SciPy, pandas, statsmodelsPyMC3scikit-learn или seaborn.

Python 3.10 добавляет несколько многомерных функций вstatistics:

  • correlation() для вычисления коэффициента корреляции Пирсона для двух переменных
  • covariance() для вычисления выборочной ковариации для двух переменных
  • linear_regression() для вычисления наклона и перехвата в линейной регрессии

Вы можете использовать каждую функцию для описания определенного аспекта взаимосвязи между двумя переменными. В качестве примера предположим, что у вас есть данные из набора сообщений в блоге—количество слов в каждом сообщении в блоге и количество просмотров каждого сообщения за определенный период времени:>>>

>>> words = [7742, 11539, 16898, 13447, 4608, 6628, 2683, 6156, 2623, 6948]
>>> views = [8368, 5901, 3978, 3329, 2611, 2096, 1515, 1177, 814, 467]

Теперь вы хотите выяснить, существует ли какая-либо (линейная) связь между количеством слов и количеством просмотров. В Python 3.10 вы можете рассчитать корреляцию между wordsviews новой correlation() функцией и с ней:>>>

>>> import statistics

>>> statistics.correlation(words, views)
0.454180067865917

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

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

Вы также можете рассчитать ковариацию между words и views. Ковариация-это еще одна мера совместной изменчивости между двумя переменными. Вы можете рассчитать его с помощью covariance():>>>

>>> import statistics

>>> statistics.covariance(words, views)
5292289.977777777

В отличие от корреляции, ковариация является абсолютной мерой. Это следует интерпретировать в контексте изменчивости самих переменных. Фактически, вы можете нормализовать ковариацию по стандартному отклонению каждой переменной, чтобы восстановить коэффициент корреляции Пирсона:>>>

>>> import statistics

>>> cov = statistics.covariance(words, views)
>>> σ_words, σ_views = statistics.stdev(words), statistics.stdev(views)
>>> cov / (σ_words * σ_views)
0.454180067865917

Обратите внимание, что это в точности соответствует вашему предыдущему коэффициенту корреляции.

Третий способ взглянуть на линейное соответствие между двумя переменными — это простая линейная регрессия. Вы выполняете линейную регрессию, вычисляя два числа, наклон и перехват, чтобы (квадратная) ошибка была сведена к минимуму в приближении количество просмотров = наклон × количество слов + перехват.

В Python 3.10 вы можете использовать linear_regression():>>>

>>> import statistics

>>> statistics.linear_regression(words, views)
LinearRegression(slope=0.2424443064354672, intercept=1103.6954940247645)

Основываясь на этой регрессии, сообщение с 10 074 словами может ожидать около 0,2424 × 10074 + 1104 = 3546 просмотров. Однако, как вы видели ранее, корреляция между количеством слов и количеством просмотров довольно слабая. Поэтому вам не следует ожидать, что это предсказание будет очень точным.

LinearRegression Объект является именованным кортежем. Это означает, что вы можете распаковать склон и перехватить его напрямую:>>>

>>> import statistics

>>> slope, intercept = statistics.linear_regression(words, views)
>>> slope * 10074 + intercept
3546.0794370556605

Здесь вы используете slope и intercept для прогнозирования количества просмотров поста в блоге с 10 074 словами.

Вы все еще хотите использовать некоторые из более продвинутых пакетов, таких как pandas и statsmodels, если вы много занимаетесь статистическим анализом. Однако с новыми дополнениями statistics в Python 3.10 у вас есть возможность более легко выполнять базовый анализ без привлечения сторонних зависимостей.

Другие довольно интересные функции

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

Кодировки текста по умолчанию

При открытии текстового файла кодировка по умолчанию, используемая для интерпретации символов, зависит от системы. В частности, locale.getpreferredencoding() используется. На Mac и Linux это обычно возвращается "UTF-8", в то время как результат в Windows более разнообразен.

Поэтому при попытке открыть текстовый файл всегда следует указывать кодировку:

with open("some_file.txt", mode="r", encoding="utf-8") as file:
    ...  # Do something with file

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

Python 3.7 ввел режим UTF-8, который позволяет заставлять ваши программы использовать кодировку UTF-8 независимо от кодировки языка. Вы можете включить режим UTF-8, указав -X utf8 исполняемому файлу параметр командной строки python или установив переменную PYTHONUTF8 среды.

В Python 3.10 вы можете активировать предупреждение, которое сообщит вам, когда текстовый файл открывается без указанной кодировки. Рассмотрим следующий сценарий, в котором не указана кодировка:

# mirror.py

import pathlib
import sys

def mirror_file(filename):
    for line in pathlib.Path(filename).open(mode="r"):
        print(f"{line.rstrip()[::-1]:>72}")

if __name__ == "__main__":
    for filename in sys.argv[1:]:
        mirror_file(filename)

Программа вернет один или несколько текстовых файлов обратно на консоль, но с каждой перевернутой строкой. Запустите программу на самой себе с включенным предупреждением о кодировании:

$ python -X warn_default_encoding mirror.py mirror.py
/home/rp/mirror.py:7: EncodingWarning: 'encoding' argument not specified
  for line in pathlib.Path(filename).open(mode="r"):
                                                             yp.rorrim #

                                                          bilhtap tropmi
                                                              sys tropmi

                                              :)emanelif(elif_rorrim fed
                  :)"r"=edom(nepo.)emanelif(htaP.bilhtap ni enil rof
                             )"}27>:]1-::[)(pirtsr.enil{"f(tnirp

                                              :"__niam__" == __eman__ fi
                                       :]:1[vgra.sys ni emanelif rof
                                           )emanelif(elif_rorrim

Обратите внимание на EncodingWarning распечатку на консоли. Опция командной строки -X warn_default_encoding активирует его. Предупреждение исчезнет, если вы укажете кодировку, например, encoding="utf-8" при открытии файла.

Бывают случаи, когда вы хотите использовать заданную пользователем локальную кодировку. Вы все еще можете сделать это, явно используя encoding="locale". Тем не менее, рекомендуется использовать UTF-8, когда это возможно. Вы можете проверить PEP 597 для получения дополнительной информации.

Асинхронная итерация

Асинхронное программирование — это мощная парадигма программирования, доступная в Python с версии 3.5. Вы можете распознать асинхронную программу по использованию async ключевого слова или специальных методов, которые начинаются с .__a like .__aiter__() или .__aenter__().

В Python 3.10 добавлены две новые асинхронные встроенные функцииaiter() и anext(). На практике эти функции вызывают .__aiter__().__anext__() специальные методы и—аналогичные обычным iter() и next()—поэтому никаких новых функций не добавляется. Это удобные функции, которые делают ваш код более читабельным.

Другими словами, в новейшей версии Python следующие операторы—где things выполняется асинхронная итерация—эквивалентны:>>>

>>> it = things.__aiter__()
>>> it = aiter(things)

В любом случае, it в конечном итоге получается асинхронный итератор. Разверните следующее поле, чтобы увидеть полный пример использования aiter() и anext():

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

Обратите внимание, что перед запуском этого кода вам необходимо установить сторонний aiofiles пакет с pip:

# line_count.py

import asyncio
import sys
import aiofiles

async def count_lines(filename):
    """Count the number of lines in the given file"""
    num_lines = 0

    async with aiofiles.open(filename, mode="r") as file:
        lines = aiter(file)
        while True:
            try:
                await anext(lines)
                num_lines += 1
            except StopAsyncIteration:
                break

    print(f"{filename}: {num_lines}")

async def count_all_files(filenames):
    """Asynchronously count lines in all files"""
    tasks = [asyncio.create_task(count_lines(f)) for f in filenames]
    await asyncio.gather(*tasks)

if __name__ == "__main__":
    asyncio.run(count_all_files(filenames=sys.argv[1:]))

asyncio используется для создания и запуска одной асинхронной задачи на имя файла. count_lines() открывает один файл асинхронно и перебирает его, используя aiter() и anext() для подсчета количества строк.

См. PEP 525, чтобы узнать больше об асинхронной итерации.

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

Контекстные менеджеры отлично подходят для управления ресурсами в ваших программах. Однако до недавнего времени их синтаксис включал необычную бородавку. Вам не разрешалось использовать круглые скобки для разбиения длинных with операторов, подобных этому:

with (
    read_path.open(mode="r", encoding="utf-8") as read_file,
    write_path.open(mode="w", encoding="utf-8") as write_file,
):
    ...

В более ранних версиях Python это вызывает invalid syntax сообщение об ошибке. Вместо этого вам нужно использовать обратную косую черту (\), если вы хотите контролировать, где вы прерываете свои строки:

with read_path.open(mode="r", encoding="utf-8") as read_file, \
     write_path.open(mode="w", encoding="utf-8") as write_file:
    ...

В то время как явное продолжение строки с обратными косыми чертами возможно в Python, PEP 8 не поощряет это. Инструмент черного форматирования полностью исключает обратную косую черту.

В Python 3.10 теперь вам разрешено добавлять круглые скобки вокруг with операторов, сколько душе угодно. Особенно если вы используете несколько контекстных менеджеров одновременно, как в приведенном выше примере, это может помочь улучшить читаемость вашего кода. Документация Python показывает несколько других возможностей с этим новым синтаксисом.

Один маленький забавный факт: заключенные в скобки with утверждения действительно работают в версии 3.9 CPython. Их реализация стала почти бесплатной с появлением синтаксического анализатора PEG в Python 3.9. Причина, по которой это называется функцией Python 3.10, заключается в том, что использование синтаксического анализатора PEG является добровольным в Python 3.9, в то время как Python 3.9 со старым синтаксическим анализатором LL(1) не поддерживает with операторы, заключенные в скобки.

Современный и безопасный протокол SSL

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

Python использует OpenSSL для различных криптографических функций, доступных в hashlibhmacssl модулях стандартной библиотеки , и. Ваша система может управлять OpenSSL, или установщик Python может включать OpenSSL.

Python 3.9 поддерживает использование любой из версий OpenSSL 1.0.2 LTS, 1.1.0 и 1.1.1 LTS. Срок службы OpenSSL 1.0.2 LTS и OpenSSL 1.1.0 истек, поэтому Python 3.10 будет поддерживать только OpenSSL 1.1.1 LTS, как описано в следующей таблице:

Открытая версия SSLPython 3.9Python 3.10Конец жизни
1.0.2 LTS20 декабря 2019 года
1.1.010 сентября 2019 года
1.1.1 LTS11 сентября 2023 года

Это прекращение поддержки более старых версий повлияет на вас только в том случае, если вам потребуется обновить системный Python на более старой операционной системе. Если вы используете macOS или Windows, или если вы устанавливаете Python с python.org или используйте (Ana)Conda, вы не увидите никаких изменений.

Однако Ubuntu 18.04 LTS использует OpenSSL 1.1.0, в то время как Red Hat Enterprise Linux (RHEL) 7 и CentOS 7 используют OpenSSL 1.0.2 LTS. Если вам нужно запустить Python 3.10 в этих системах, вам следует подумать о том, чтобы установить его самостоятельно, используя либо python.org или установщик Conda.

Отказ от поддержки старых версий OpenSSL сделает Python более безопасным. Это также поможет разработчикам Python в том, что код будет легче поддерживать. В конечном счете, это поможет вам, потому что ваш опыт работы с Python будет более надежным. См. PEP 644 для получения более подробной информации.

Дополнительная информация о вашем интерпретаторе Python

sys Модуль содержит много информации о вашей системе, текущей среде выполнения Python и выполняемом в данный момент скрипте. Вы можете, например, узнать о путях, по которым Python ищет модули, sys.path и просмотреть все модули, которые были импортированы в текущем сеансе sys.modules.

В Python 3.10 sys появились два новых атрибута. Во-первых, теперь вы можете получить список имен всех модулей в стандартной библиотеке:>>>

>>> import sys

>>> len(sys.stdlib_module_names)
302

>>> sorted(sys.stdlib_module_names)[-5:]
['zipapp', 'zipfile', 'zipimport', 'zlib', 'zoneinfo']

Здесь вы можете видеть, что в стандартной библиотеке около 300 модулей, некоторые из которых начинаются с буквы z. Обратите внимание, что в списке указаны только модули и пакеты верхнего уровня. Подпакеты, например importlib.metadata, не получают отдельной записи.

Вероятно, вы будете пользоваться им не sys.stdlib_module_names так часто. Тем не менее, список хорошо сочетается с аналогичными функциями самоанализа, такими как keyword.kwlist и sys.builtin_module_names.

Одним из возможных вариантов использования нового атрибута является определение того, какие из импортируемых в настоящее время модулей являются зависимостями сторонних производителей:>>>

>>> import pandas as pd
>>> import sys

>>> {m for m in sys.modules if "." not in m} - sys.stdlib_module_names
{'__main__', 'numpy', '_cython_0_29_24', 'dateutil', 'pytz',
 'six', 'pandas', 'cython_runtime'}

Вы находите импортированные модули верхнего уровня, просматривая имена, в sys.modules которых в названии нет точки. Сравнивая их со стандартными именами модулей библиотеки, вы обнаруживаете, что numpy, dateutil, и pandas являются некоторыми импортированными сторонними модулями в этом примере.

Другим новым атрибутом является sys.orig_argv. Это связано с sys.argv тем , что содержит аргументы командной строки, данные вашей программе при ее запуске. Напротив, sys.orig_argv в нем перечислены аргументы командной строки, переданные самому python исполняемому файлу. Рассмотрим следующий пример:

# argvs.py

import sys

print(f"argv: {sys.argv}")
print(f"orig_argv: {sys.orig_argv}")

Этот сценарий повторяет списки orig_argv и .argv Запустите его, чтобы увидеть, как собирается информация:

$ python -X utf8 -O argvs.py 3.10 --upgrade
argv: ['argvs.py', '3.10', '--upgrade']
orig_argv: ['python', '-X', 'utf8', '-O', 'argvs.py', '3.10', '--upgrade']

По сути, все аргументы, включая имя исполняемого файла Python—заканчиваются orig_argv. Это в отличие от argv, который содержит только аргументы, которые не обрабатываются сами по python себе.

Опять же, это не та функция, которую вы будете часто использовать. Если вашей программе нужно позаботиться о том, как она выполняется, вам обычно лучше полагаться на информацию, которая уже доступна, вместо того, чтобы пытаться проанализировать этот список. Например, вы можете выбрать строгий zip() режим только в том случае, если ваш скрипт не работает с флагом оптимизирован , -O например, так:

list(zip(names, set_numbers, num_pieces, strict=__debug__))

__debug__ Флаг устанавливается при запуске интерпретатора. Это будет False, если вы работаете pythonс -O или -OO указано, и True в противном случае. Использование __debug__ обычно предпочтительнее "-O" not in sys.orig_argv или какой-либо аналогичной конструкции.

Одним из мотивирующих вариантов использования sys.orig_argv является то, что вы можете использовать его для создания нового процесса Python с теми же или измененными аргументами командной строки, что и ваш текущий процесс.

Будущие аннотации

Аннотации были введены в Python 3, чтобы дать вам возможность прикреплять метаданные к переменным, параметрам функций и возвращаемым значениям. Они чаще всего используются для добавления подсказок типа в ваш код.

Одна из проблем с аннотациями заключается в том, что они должны быть допустимым кодом Python. Во-первых, это затрудняет ввод рекурсивных классов подсказок. В PEP 563 была введена отложенная оценка аннотаций, что позволило аннотировать имена, которые еще не были определены. Начиная с Python 3.7, вы можете активировать отложенную оценку аннотаций с __future__ помощью импорта:

from __future__ import annotations

Намерение состояло в том, что отложенная оценка станет дефолтом в какой-то момент в будущем. После саммита по языку Python 2020года было решено сделать это в Python 3.10.

Однако после дополнительного тестирования стало ясно, что отложенная оценка плохо работает для проектов, использующих аннотации во время выполнения. Ключевые люди в проектах FastAPI и Pydantic высказали свои опасения. В последнюю минуту было решено перенести эти изменения на Python 3.11.

Чтобы облегчить переход к будущему поведению, в Python 3.10 также было внесено несколько изменений. Самое главное, inspect.get_annotations() что была добавлена новая функция. Вы должны вызвать это, чтобы получить доступ к аннотациям во время выполнения:>>>

>>> import inspect

>>> def mean(numbers: list[int | float]) -> float:
...     return sum(numbers) / len(numbers)
...

>>> inspect.get_annotations(mean)
{'numbers': list[int | float], 'return': <class 'float'>}

Ознакомьтесь с рекомендациями по аннотациям для получения более подробной информации.

Как обнаружить Python 3.10 во время выполнения

Python 3.10-это первая версия Python с двузначным второстепенным номером версии. Хотя это в основном интересный забавный факт и указание на то, что Python 3 существует уже довольно давно, он также имеет некоторые практические последствия.

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

# bad_version_check.py

import sys

# Don't do the following
if sys.version < "3.6":
    raise SystemExit("Only Python 3.6 and above is supported")

В Python 3.10 этот код вызовет SystemExit и остановит вашу программу. Это происходит потому, что, как строки, "3.10" меньше, чем "3.6".

Правильный способ сравнения номеров версий-использовать кортежи чисел:

# good_version_check.py

import sys

if sys.version_info < (3, 6):
    raise SystemExit("Only Python 3.6 and above is supported")

sys.version_info это объект кортежа, который вы можете использовать для сравнений.

Если вы проводите такого рода сравнения в своем коде, вам следует проверить свой код с помощью flake8-2020, чтобы убедиться, что вы правильно обрабатываете версии:

$ python -m pip install flake8-2020

$ flake8 bad_version_check.py good_version_check.py
bad_version_check.py:3:4: YTT103 `sys.version` compared to string
                          (python3.10), use `sys.version_info`

После flake8-2020 активации расширения вы получите рекомендацию о замене sys.version на sys.version_info.

Итак, следует ли Вам перейти на Python 3.10?

Теперь вы увидели самые крутые функции новейшей и последней версии Python. Теперь вопрос в том, следует ли вам перейти на Python 3.10, и если да, то когда вам следует это сделать. При рассмотрении вопроса об обновлении до Python 3.10 необходимо учитывать два различных аспекта:

  1. Следует ли вам обновить свою среду, чтобы запускать код с помощью интерпретатора Python 3.10?
  2. Следует ли вам писать свой код с использованием новых функций Python 3.10?

Очевидно, что если вы хотите протестировать сопоставление структурных шаблонов или любые другие интересные новые функции, о которых вы читали здесь, вам нужен Python 3.10. Можно установить последнюю версию параллельно с вашей текущей версией Python. Простой способ сделать это-использовать менеджер среды, такой как pyenv или Conda. Вы также можете использовать Docker для запуска Python 3.10 без его локальной установки.

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

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

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

Если вы распространяете приложение или библиотеку, которые вместо этого используются другими, возможно, вам захочется быть немного более консервативным. В настоящее время Python 3.6 является самой старой официально поддерживаемой версией Python. Срок его службы истекает в декабре 2021 года, после чего Python 3.7 станет минимальной поддерживаемой версией.

Документация включает полезное руководство по переносу кода на Python 3.10. Проверьте это для получения более подробной информации!

Вывод

Выпуск новой версии Python всегда стоит отметить. Даже если вы не сможете начать использовать новые функции сразу, они станут широко доступными и станут частью вашей повседневной жизни в течение нескольких лет.

В этом уроке вы увидели такие новые функции, как:

  • Более дружелюбные сообщения об ошибках
  • Мощное сопоставление структурных шаблонов
  • Улучшения подсказки типа
  • Более безопасная комбинация последовательностей
  • Новые функции статистики