Программы
Обзор изменений в Python 3.7

Обзор изменений в Python 3.7

На днях вышла новая версия Python – почти 1.5 года разработки. Посмотрим, что же подвезли нам в этот раз.

На днях вышла новая версия 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__.

Изображение Python 3.11. Что нового?

Работа над системой типов

Ускорили 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.