На днях вышла новая версия Python – почти 1.5 года разработки. Посмотрим, что же подвезли нам в этот раз.
Новое C API для работы с thread local и в этот же пункт contextvars - context local переменные. Суть довольно простая: есть у нас глобальные переменные, которые зависят (могут быть модифицированы) от текущего треда – используем
static __thread char *p;
– само собой, зависит от платформы и т.д. Но так мы привязываем переменную к треду, в котором она используется. Классический пример – errno – глобальная переменная, в которой лежит последняя ошибка. Будет обидно, если один тред перетрёт эту информацию для другого. И, есть сильное подозрение, что новое C API было сделано как раз в рамках добавления contextvars.
Аналогичная проблема имеется при асинхронном программировании – создали мы транзакцию в базе данных, кинули запрос, await-им его – оп переключение контекста, попадаем в ту же транзакцию. Такое поведение (два несогласованных запроса в рамках одной транзакции) может вызвать некорректное поведение.
Поэтому было бы неплохо положить подобную переменную в конкретный context storage, получать её, например по номеру текущего контекста. А лучше – автоматом получать номер контекста, а по нему – нужный экземпляр переменной. Именно это и сделали.
Итог: довольно приятное обновление – можно проще писать конкурентный код, работающий с общими ресурсами. Пожалуй, самое важное изменение этой версии.
Новый формат .pyc файлов – теперь помимо времени модификации файла используется хеш от оригинального файла. В общем, вторая извечная проблема – синхронизации кешей наконец-то достала разработчиков. Но не совсем – всё равно есть режим совместимости, есть разные режимы, позволяющие всё также не проверять при подгрузке на лету.
Итог: будет меньше обидных косяков для новичков. Остальные могут выкинуть алиас pyclean:
pyclean () { find . -type f -name "*.py[co]" -delete find . -type d -name "__pycache__" -delete }
– хотя, я бы не торопился.
Добавлен модуль importlib.resources
– попытка
стандартизировать работу с файлами ресурсов. На данный момент получение
доступа к файлам ресурсов происходит чаще всего с помощью __file__
– адресация относительно текущего файла (исходя из личных наблюдений).
Также существует более
"правильный" вариант – setuptools.pkg_resources
. Он более медленный
и вероятность увидеть его использование довольно небольшая.
Есть ещё вариант захардкодить пути... Мы не будем даже о нём говорить.
Так вот, теперь можно получить файл из нужного пакета. Например:
example/ │ ├── alice_in_wonderland.txt └── __init__.py
– тогда мы можем получить доступ к ресурсу "alice_in_wonderland.txt", лежащему в пакете "example" следующим образом:
>>> from importlib import resources >>> with resources.open_text("example", "alice_in_wonderland.txt") as fid: ... alice = fid.readlines() ... >>> print("".join(alice[:7]))
Важны тут абстракции пакета (определён как Union[str, ModuleType]
)
и ресурса (определён как Union[str, os.PathLike]
).
То есть путь до ресурса пишем в виде пути от корня пакета.
Это также решает проблему с пакетам, которые хранятся в виде zip-файлов, где __file__ нам уже не поможет.
Итог: ещё один способ прочитать файл... Далеко не факт, что
программисты откажутся от интуитивно понятного __file__
.
Работа над системой типов
Ускорили typing, добавили generic типы и добавили отложенное исполнение аннотаций. Ускорение – всегда приятно, но к новшествам сложно отнести. Отложенное исполнение аннотаций – приятный "фикс" синтаксиса. Сравним 2 кода:
class C: @classmethod def from_string(cls, source: str) -> 'C': ...
и
class C: @classmethod def from_string(cls, source: str) -> C: ...
– да, "просто" убрали кавычки, но на один раздражающий момент меньше.
Напомню, что раньше нельзя было ссылаться на тип, который ещё не определён. А в случае с описанием методов класса это был довольно распространённый способ применения.
Однако, не всё так радужно – использовать это можно только включив нужную прагму:
from __future__ import annotations
– проблемы с совместимостью.
Обобщённые типы (generic) – вполне логичное развитие идей типизации. Если нам нужно написать код, который единообразно работать с разными типами, к тому же он не зависит от их контрактов, можно абстрагироваться от типов:
from typing import Sequence, TypeVar T = TypeVar('T') # Declare type variable def first(l: Sequence[T]) -> T: # Generic function return l[0]
Как и прочие аннотации, они не бьют нам по рукам, если мы делаем что-то неправильно. Но можно использовать средства IDE для более качественного анализа кода.
Итог: да, система типов развивается, но ожидать чего-то революционного не стоит. Разве что в Python 4. А пока это скорее "бижутерия" – дешёвый способ сделать красиво. Ну хоть так!
Менее значимые изменения
__getattr__
для модулей – развитие идей "всё есть объект"
и динамики во все поля. Ещё больше возможностей писать непонятный код и разную
магию.
Декоратор @dataclass
– уже имеющийся
collections.namedtuple
, но с человеческим лицом.
Новая функция breakpoint()
– вставить точку останова для
дальнейшей отладки. По сути – алиас к
import pdb pdb.set_trace()
Функции time
с поддержкой наносекунд:
time.clock_gettime_ns() time.clock_settime_ns() time.monotonic_ns() time.perf_counter_ns() time.process_time_ns() time.time_ns()
В классы str, bytes и bytearray добавлен метод isascii() для проверки строки на наличие только ASCII-символов.
Оптимизации
- Снижены накладные расходы при вызове многих методов из стандартной библиотеки.
- В целом методы теперь вызываются на 20% быстрее.
- Время запуска самого Python снижено на 10-30%.
- Импортирование typing теперь быстрее в 7 раз.
- Ряд оптимизаций asyncio.