Если вам хочется попробовать все фичи великолепной последний версии 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-разработчик, где мы делаем универсальных специалистов, которые высоко ценятся на рынке.
Узнайте, как прокачаться в других специальностях или освоить их с нуля:
Другие профессии и курсы
ПРОФЕССИИ
КУРСЫ
merhalak
Ну, одно дело перевод в строку, а потом подсчёт единиц, другое — bit_count.
tyomitch
Я видал в продакшн-коде, как
htonl()
был реализован черезhex()
, строковые манипуляции, иint(,16)
:-/0xd34df00d
Как насчёт сравнения числа с нулем через перевод в строку и проверку, является ли первый символ минусом?
Впрочем, надо отдать должное, автор был последователем defensive programming и проверял, является ли результат перевода числа в строку не пустой строкой.
maxzhurkin
int.bit_count (если верить официальной документации) считает именно количество единиц:
merhalak
Я косо выразился: эффективность этого метода, перегоном через строку — крайне мала.
Как заметил наш коллега, есть куда более эффективный способ считать единицы в int'е, пусть и для Python этот вариант придется адаптировать из-за поддержки больших чисел.
tyomitch
Эффективный, но непереносимый :)
Фактически используемый код:
Сумеет ли компилятор свернуть это в одну инструкцию процессора — зависит и от компилятора, и от процессора.
merhalak
Всегда к этому можно добавить необходимые оптимизации в виде (я не пишу на си/плюсах, точных объявлений макросов не знаю):