Краще пізно, ніж ніколи: нові фічі Python 3.9

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

Мене звати Володимир Данилевський, я працюю на позиції Python Developer в компанії Django Stars. Моя стаття пояснює нові фічі Python 3.9, реліз якого був 5 жовтня 2020 року.

Останній реліз Python 3.9 містить фічі для зворотної сумісності, як, наприклад, collections.abc.Mapping, що буде видалений у Python 3.10. Використовуйте тестовий режим щоб побачити всі deprecated errors у вашому коді.

IANA база часових поясів PEP 615

Наразі додалась ще одна «batteries» — zoneinfo.ZoneInfo

Спойлер: для Windows користувачам потрібно завантажити tzdata: python -m pip install tzdata

Цей модуль допоможе для tz-aware timestamps. Так, наприклад:

From datetime import datetime
From zoneinfo import ZoneInfo

timestamp = datatime.now(tz=ZoneInfo("Europe/Kiev"))

Він буде мати читабельну мітку часового поясу, також таким же чином можливо змінити таймзону: timestamp = timestamp.astimezone(ZoneInfo("Europe/London"))

Виконавши команду zoneinfo.available_timezones() можна отримати всі можливі лейби для ZoneInfo, які можна використати для timestamps.

Також цей функціонал можна імпортувати у минулі версії Python через модуль backports який є до версії 3.6 backward compatibility.

try:
    Import zoneinfo
except ImportError:
    from backports import zoneinfo

Новий синтаксис оновлення словників PEP 584

Одна з основних дата-структур отримала новий оператор злиття.

Раніше були наступні види злиття словарів:

>>> fifa_hosts = {2006: "Germany", 2010: "South Africa", 2014: "Brazil"}
>>> winter_olympics = {2006: "Italy", 2010: "Canada", 2018: "South Korea"}

>>> {**fifa_hosts, **winter_olympics}
{2006: 'Italy', 2010: 'Canada', 2014: 'Brazil', 2018: 'South Korea'}

>>> merged = fifa_hosts.copy()
>>> for key, value in winter_olympics.items():
...    merged[key] = value
...
>>> merged
{2006: 'Italy', 2010: 'Canada', 2014: 'Brazil', 2018: 'South Korea'}

Тепер з’явились два нових методи Python для словарів: злиття (|) та in-place злиття (|=).

>>> winter_olympics = {2006: "Italy", 2010: "Canada", 2018: "South Korea"}
>>> fifa_hosts | winter_olympics
{2006: 'Italy', 2010: 'Canada', 2014: 'Brazil', 2018: 'South Korea'}

>>> fifa_hosts |= winter_olympics
>>> fifa_hosts
{2006: 'Italy', 2010: 'Canada', 2014: 'Brazil', 2018: 'South Korea'}

Більш гнучке використання декораторів PEP 614

Зазвичай декоратор має бути іменованим callable objecte — функція або class з __call__ методом. PEP 614 дозволяє бути любим callable. Приклад:

import functools

def normal(func):
    return func

def shout(func):
    @functools.wraps(func)
    def shout_decorator(*args, **kwargs):
        return func(*args, **kwargs).upper()

    return shout_decorator

def whisper(func):
    @functools.wraps(func)
    def whisper_decorator(*args, **kwargs):
        return func(*args, **kwargs).lower()

    return whisper_decorator
DECORATORS = {"normal": normal, "shout": shout, "whisper": whisper}

voice = input(f"Choose your voice ({', '.join(DECORATORS)}): ")

@DECORATORS[voice]
def get_story():
    return """
        Alice was beginning to get very tired of sitting by her sister on the
        bank, and of having nothing to do: once or twice she had peeped into
        the book her sister was reading, but it had no pictures or
        conversations in it, "and what is the use of a book," thought Alice
        "without pictures or conversations?"
        """

print(get_story())

Покращення

Нові методи для str — removeprefix, removesuffix PEP 616

На перший погляд, здається, що це ще один велосипед до вже існуючих: .strip .lstrip .rstrip

Але можна побачити, що вони використовують переданий параметр як набір символів для видалення, а не півстроку. Тому наприклад:

'123 321'.strip('12')
'3 3'

Це вже давно є причиною створення подібних баг-репортів

Нові методи .removesuffix</cpde> <code>.removeprefix видаляють одне входження підстроки і повертає нову строку з видаленою підстрокою.

Більш гнучкі type annotations та type hints PEP 593

Анотації для функцій та змінних з’явилися ще в Python 3.0. Це додало можливість анотувати довільною інформацією про змінні та зворотні значення. Зараз подібне використання майже повністю витіснено type hints — за допомогою mypy можливо провести статичний аналіз коду.

Тепер на додачу до звичайних type hints можна додати коментар що буде доступний в __annotations__ та в хелпері get_type_hints

З модуля typing

from typing import Annotated
 
Leg = Annotated[float, "Side leg lenght"]
Hypotenuse = Annotated[float, "Hypotenuse lenght"]
 
def calculate_leg_lenght(existing_leg: Leg, hypotenuse: Hypotenuse) -> Leg:
   return (hypotenuse**2 - existing_leg**2) ** 0.5

Новий більш потужний парсер PEP 617

Найбільш масштабною змінною став реліз нового парсер. Хоча цього не видно, але це важливий крок на шляху нових рішень та розширення існуючого функціоналу. З самого початку Python використовув простий LL(1) parser для парсингу коду в дерево. Парсить це він в один прохід без повернень, з чого витікають обмеження та проблеми. Так наприклад, це дозволить реалізувати tructural pattern matching (PEP 634).

Guido van Rossum зробив детальний розбір переваг та деталей щодо нового парсеру — PEG (parsing expression grammar) parsers.

Зараз головною ціллю core devs є приводження AST нового парсера до вигляду результату зі старого. Якщо у вас виникають проблеми, або ви хочете порівняти результати нового і старого парсеру, можна використати -X oldparser флаг інтерпретатору.

Покращення швидкодії та багато іншого

Покращено присвоювання змінної в comprehension expression. Присвоювання у конструкції for variable in [some_iterable] тепер таке ж швидке як і звичайне variable = some_value.

Покращено операцію floor_division(//) — тепер вона ~ приблизно на 35% швидше.

Python 3.6.6
timeit.timeit('a = 3.5; b = a // 2', number=500000)
0.05193754100002934

Python 3.9.3 
timeit.timeit('a = 3.5; b = a // 2', number=500000)
0.02754197199999453

Під капотом покращили виклики для створення таких built-ins як — range, tuple, set, frozenset, list, dict завдяки використанню нового «vectorcall protocol» PEP 590. На практиці я не можу це підтвердити як і в треді розробників
UPD. Дякую Дмитру, що вказав на мою помилку, нище наведені оновленні результати.

Python 3.8.9
 python3 -m timeit "dict(**{'a': 2, 'b': 4, 'c': 6, 'd': 8})"
1000000 loops, best of 5: 275 nsec per loop
Python 3.9.3
python3 -m timeit "dict(**{'a': 2, 'b': 4, 'c': 6, 'd': 8})"
500000 loops, best of 5: 470 nsec per loop

Також для коротких ASCII строк пришвидшили `decode` метод так, наприклад приблизно на ~15% швидше:

Python 3.8.9
python3 -m timeit 'd={}; repr(d)' 
2000000 loops, best of 5: 196 nsec per loop

Python 3.9.3
python3 -m timeit 'd={}; repr(d)' 
2000000 loops, best of 5: 179 nsec per loop

Але загалом це мало вплинуло на перфоманс, так зі сторінки патч ноута ми можемо прослідкувати за прогресом:

Python version       3.4     3.5     3.6     3.7     3.8    3.9
--------------       ---     ---     ---     ---     ---    ---

Variable and attribute read access:
read_local           7.1     7.1     5.4     5.1     3.9    3.9
read_nonlocal        7.1     8.1     5.8     5.4     4.4    4.5
read_global          15.5    19.0    14.3    13.6    7.6    7.8
read_builtin         21.1    21.6    18.5    19.0    7.5    7.8
read_classvar_f_c    25.6    26.5    20.7    19.5    18.4   17.9
read_classvar_f_i    22.8    23.5    18.8    17.1    16.4   16.9
read_instancevar     32.4    33.1    28.0    26.3    25.4   25.3
read_instancevar_s   27.8    31.3    20.8    20.8    20.2   20.5
read_namedtuple      73.8    57.5    45.0    46.8    18.4   18.7
read_boundmethod     37.6    37.9    29.6    26.9    27.7   41.1

Variable and attribute write access:
write_local          8.7     9.3     5.5     5.3     4.3    4.3
write_nonlocal       10.5    11.1    5.6     5.5     4.7    4.8
write_global         19.7    21.2    18.0    18.0    15.8   16.7
write_classvar       92.9    96.0    104.6   102.1   39.2   39.8
write_instancevar    44.6    45.8    40.0    38.9    35.5   37.4
write_instancevar_s  35.6    36.1    27.3    26.6    25.7   25.8

Data structure read access:
read_list            24.2    24.5    20.8    20.8    19.0   19.5
read_deque           24.7    25.5    20.2    20.6    19.8   20.2
read_dict            24.3    25.7    22.3    23.0    21.0   22.4
read_strdict         22.6    24.3    19.5    21.2    18.9   21.5

Data structure write access:
write_list           27.1    28.5    22.5    21.6    20.0   20.0
write_deque          28.7    30.1    22.7    21.8    23.5   21.7
write_dict           31.4    33.3    29.3    29.2    24.7   25.4
write_strdict        28.4    29.9    27.5    25.2    23.1   24.5

Stack (or queue) operations:
list_append_pop      93.4   112.7    75.4    74.2    50.8   50.6
deque_append_pop     43.5   57.0     49.4    49.2    42.5   44.2
deque_append_popleft 43.7   57.3     49.7    49.7    42.8   46.4

Timing loop:
loop_overhead         0.5     0.6     0.4     0.3     0.3    0.3

Висновки

Загалом, вводить декілька речей для Quality-of-Life, але найголовніше всередині з новим парсером, що дозволить нові фічі вже у версії 3.10.

Перед використанням важливо перевірити чи немає у вас функціоналу, що був видалений та речей що змінились

Так на сьогодні, остання версія — 3.9.4 яка вийшла 4 квітня, вже мала декілька bug-fixes та виправлених вразливостей.

👍НравитсяПонравилось5
В избранноеВ избранном5
LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

pingtop — 🏓Ping multiple servers and show results in a top-like terminal UI.

Ping multiple servers and show the result in a top like terminal UI. There is a dependency (blist) not supporting Python3.9, so please pingtop can’t support 3.9.

Це в принципі все, що варто знати про Python в цілому, і про Python 3.9 зокрема.

Тулза вимагає пітон 3.8, бо в 3.9 знов щось розвалили, і бібліотека, яку вони використовують, з ним не сумісна.

Мені треба мати заінстальовпними всеможлиаі версії пітона? А цей серпентарій між собою не перегризеться?

Для цього є віртуральні оточення такі як pipenv, venv і т.п.

Я як простий юзер хочу запустити pingtop.

Вивчати всю екосистему розробки на пітоні?

Зрозуміло, чому стає популярним Go. І чому С буде жити вічно.

Використовуй тоді аналоги на го і с. Порівнюти компільовані мови і динамічно виконувані некоректно, у них різне призначення.

Гугл підсуває симпатичну тулзу на пітоні.

Але я згідний з тим, що пітон для таких застосувань зараз явно не готовий. Та й можливо це не його ніша, спільнота та й взагалі.

В процесі спроб це завести я собі зніс NumPy від робочого пітона 3.5, в результаті переінстальовував його в полі через мобільний інтернет, але йой, най буде.

Але я згідний з тим, що пітон для таких застосувань зараз явно не готовий. Та й можливо це не його ніша, спільнота та й взагалі.

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

Я глянув чому воно не підтримує пітон 3.9. Модуль blist не оновлювався вже 8 (судячи з pip історії) років.
По інсталяції, треба було використати pipenv, якщо ця тулза так конче була необхідна. Там легко можна вказати версію пітону.

Вивчати всю екосистему розробки на пітоні?

Одна команда virtualenv — досить в даній ситуації.

Зрозуміло, чому стає популярним Go. І чому С буде жити вічно.

На C подібні проблеми в десятки разів частіше, але всі знають, що бібліотеки змінюються, іноді несумісно, і ніхто так особливо не плаче.

Go не використовував, але подібні проблеми несумісности можуть на ньому зʼявлятись з тих же причин.
До речі, он у 1.17, кажуть, змінили calling convention у своїх викликах, і зроблять це ж у всій асемблерній підкладці — то теж весь код міняти?

До речі про 1.17:

> This change does not affect the functionality of any safe Go code and is designed to have no impact on most assembly code. It may affect code that violates the unsafe.Pointer rules when accessing function arguments, or that depends on undocumented behavior involving comparing function code pointers. To maintain compatibility with existing assembly functions, the compiler generates adapter functions that convert between the new register-based calling convention and the previous stack-based calling convention. These adapters are typically invisible to users, except that taking the address of a Go function in assembly code or taking the address of an assembly function in Go code using reflect.ValueOf(fn).Pointer() or unsafe.Pointer will now return the address of the adapter. Code that depends on the value of these code pointers may no longer behave as expected.

А відловити це буде тяжко...

Я як простий юзер хочу запустити pingtop.

Тоді ви будете використовувати цю тулзу як пакет для вашої ОС і можливо навіть не будете знати, що вона на пітоні
Або як пакет для дефолтної версії пітона для вашої ОС

Зрозуміло, чому стає популярним Go

Yeah www.tiobe.com/tiobe-index

простий юзер який топить за компіляцію під конкретний незмінний рантайм і не юзає докер — серйозно?

Деколи просто потрібен ping на багато нод, тут і зараз.

(Знайшов fping — не така гарна картинка, але робочий)

я в таких випадках беру те що є під рукою (наприклад ансібл, баш/курл..). Я мав на увазі те що мова ніяк не має залежати від лівих ліб, тобто те що там щось так написано що не працює в наступній мінорній версії рантайму — справа лише ліби, не до розробників пайтону претензії

Мова невід’ємна від культури ;)

Мені стало цікаво, пішов дивитися в сторону nmap і знайшов у них nping. Але, в коментах на СО зустрів гарне юніксвейне рішення — `parallel -u ping ::: host1 host2 host3`

Якщо не потрібна велика конкурентність, то і for підійде

В нього та сама проблема, що і в іншого юніксвейного рішення — нема синхронізації, дані преплутуються. Коли нод з 15, важко відслідковувати. Крім того, parallel чомусь не працює з which.

Тобто воно нічим не краще за
for ip in 1 2; do bash -c "ping 127.0.0.$ip &"; done

Він вміє групувати вивід. Це в оригінальному посту нащось -u додали.
 $ parallel ping -c 3 ::: dou.ua linux.org.ru github.com     PING linux.org.ru (178.248.233.6) 56(84) bytes of data. 64 bytes from 178.248.233.6 (178.248.233.6): icmp_seq=1 ttl=48 time=84.6 ms 64 bytes from 178.248.233.6 (178.248.233.6): icmp_seq=2 ttl=48 time=85.8 ms 64 bytes from 178.248.233.6 (178.248.233.6): icmp_seq=3 ttl=48 time=83.6 ms --- linux.org.ru ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2000ms rtt min/avg/max/mdev = 83.606/84.670/85.811/0.901 ms PING github.com (140.82.121.3) 56(84) bytes of data. 64 bytes from lb-140-82-121-3-fra.github.com (140.82.121.3): icmp_seq=1 ttl=49 time=77.3 ms 64 bytes from lb-140-82-121-3-fra.github.com (140.82.121.3): icmp_seq=2 ttl=49 time=73.3 ms 64 bytes from lb-140-82-121-3-fra.github.com (140.82.121.3): icmp_seq=3 ttl=49 time=72.0 ms --- github.com ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2001ms rtt min/avg/max/mdev = 71.951/74.175/77.277/2.260 ms PING dou.ua (178.79.140.30) 56(84) bytes of data. 64 bytes from li196-30.members.linode.com (178.79.140.30): icmp_seq=1 ttl=48 time=89.3 ms 64 bytes from li196-30.members.linode.com (178.79.140.30): icmp_seq=2 ttl=48 time=86.9 ms 64 bytes from li196-30.members.linode.com (178.79.140.30): icmp_seq=3 ttl=48 time=98.9 ms --- dou.ua ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2002ms rtt min/avg/max/mdev = 86.887/91.710/98.946/5.210 ms

Крім того, parallel чомусь не працює з which.

parallel $(which ping) -c 3 ::: dou.ua
Це мається на увазі?

конкретно в цьому випадку винен не пітон, а ліба, яка через жопу написана

github.com/...​elStutzbach/blist/pull/91

Я не проти, якби pip3 мені проінсталював потрібного пітона.

Але це весь софт такий — рідко що працює, переважно все закінчується стектрейсом, бо щось пішло не так.

о, таки побачив цей комент в треді, плюсую =)

Если я ничего не путаю, то тест с созданием dict() некорректен ибо там стоит флаг -s (setup) и само создание словаря выполняется один раз дальше выполняется 50000 null-операций.

Там зʼявилась нова мова, на основі синтаксису пітону. Тільки немає gil, та компілюється в машинний код. Але, їй тільки півроку.
github.com/kuroko-lang/kuroko

«Europe/Kiev»

чогось одразу не зайшло)

Теж засмутився з цього факту, коли шукав потрібну зону в

zoneinfo.available_timezones() 

А в Сімферополі яка зараз часова зона?

где про баги ? основная проблема покращень — горка новых багов которые вдруг все рушат

Пожожди, еще в 3.10 паттерн матчинг зальют

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

1. раздутая стандартная библиотека с кучей легаси
2. нет вменяемого стандартного пакетного менеджера
3. нет возможности реюзать синхронный код в асинхронном и наоборот (как, например, в Go)
4. невозможно нормально работать с мультитредами из-за GIL

Ну и еще по мелочи:
— Слабая поддержка ФП
— хотелось бы JIT

JIT обіцяють з 3.12.

1. раздутая стандартная библиотека с кучей легаси

Что именно по-вашему легаси?

3. нет возможности реюзать синхронный код в асинхронном и наоборот (как, например, в Go)

Ну почему же нельзя. В асинхронном любые синхронные вызовы просто будет его блокировать, больше вреда не будет (а где это неизбежно, но надо параллелить — есть пулы ниток). Для синхронного есть несколько стандартных методов сделать однократное включение движка asyncio на один вызов.
Конечно, надо понимать последствия и типовые грабли, но всё вполне успешно решается...

4. невозможно нормально работать с мультитредами из-за GIL

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

— Слабая поддержка ФП

А какая будет сильная, в полностью-то динамическом языке?

— хотелось бы JIT

PyPy даёт уже неплохой JIT, где не мешает динамическая природа языка.
С этой точки зрения, лучше бы иметь возможности категорически убивать динамичность там, где она не нужна — например, возможность по ходу жизни объекта переопределить любой его метод и даже поменять класс на ходу.
Обязательные объявления переменных (по заказу, как user strict в Perl или implicit none в Fortran).
Вот после этого уже были бы шансы дать JITʼу реальные возможности для работы.

Подписаться на комментарии