Python 3.7

good-penguin.png

Спустя полтора года после выхода предыдущей мажорной версии, наконец-то состоялся релиз Python 3.7.

В этом выпуске

  • Улучшена поддержка аннотации типов
  • Data classes
  • Атрибуты модулей
  • Отладка с помощью breakpoint()
  • И многое другое

PEP 563, Отложенное исполнение аннотаций типов

Теперь аннотации разрешаются в момент вызова функции, а не в момент загрузки её кода. Это уменьшает время старта программы и делает доступным использование имён, определённых позднее самой функции (forward references).

Такой код вызывал бы ошибку в предыдущих версиях Python

class C:
    @classmethod
    def from_string(cls, source: str) -> C:
        ...

    def validate_b(self, obj: B) -> bool:
        ...

class B:
    ...

Это изменение нарушает совместимость, поэтому до прихода версии Python 4.0 пока требует

from __future__ import annotations

PEP 553, Встроенная функция breakpoint ()

Отладка стала ещё проще! Допустим, у вас есть такой код

def divide(e, f):
    return f / e

a, b = 0, 1
print(divide(a, b))

В предыдущих версиях для отладки вам требовалось:

def divide(e, f):
    import pdb; pdb.set_trace()
    return f / e

В 3.7 это выглядит проще и короче

def divide(e, f):
    breakpoint()
    return f / e

Теперь запускайте ваш код:

$ python3.7 bugs.py 
> /home/gahjelle/bugs.py(3)divide()
-> return f / e
(Pdb)

По умолчанию breakpoint() просто заменяется на import pdb; pdb.set_trace(), однако это можно изменить. Скажем, вы можете использовать другой отладчик:

$ PYTHONBREAKPOINT=pudb.set_trace python3.7 bugs.py

Или отключить отладку вовсе
$ PYTHONBREAKPOINT=0 python3.7 bugs.py
ZeroDivisionError: division by zero

Или запустить IPython
$ PYTHONBREAKPOINT=IPython.embed python3.7 bugs.py 
IPython 6.3.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: print(e / f)
0.0

В конечном счёте, вы можете написать свой собственный обработчик breakpoint()

PEP 557, Data Classes

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

from dataclasses import dataclass, field

@dataclass(order=True)
class Country:
    name: str
    population: int
    area: float = field(repr=False, compare=False)
    coastline: float = 0

    def beach_per_person(self):
        """Meters of coastline per person"""
        return (self.coastline * 1000) / self.population

Методы __init__, __repr__, __eq__, __ne__, __lt__, __le__, __gt__, __ge__ будут сгенерированы автоматически для класса Country

PEP 562, Кастомизация атрибутов модулей

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

PEP 564, Временны́е функции с наносекундным разрешением

Улучшен модуль time, добавлено несколько новых функций

time.clock_gettime_ns()
time.clock_settime_ns()
time.monotonic_ns()
time.perf_counter_ns()
time.process_time_ns()
time.time_ns()

Точность расчёта временных интервалов повышена до наносекунд

Упорядоченные словари

Порядок ключей в словарях теперь гарантированно совпадает с порядком их вставки, это добавлено в спецификацию.

>>> {"one": 1, "two": 2, "three": 3}  # Python <= 3.5
{'three': 3, 'one': 1, 'two': 2}

>>> {"one": 1, "two": 2, "three": 3}  # Python >= 3.6
{'one': 1, 'two': 2, 'three': 3}

Это работало ещё с версии 3.6, однако опиралось на внутреннюю реализацию словарей CPython, и полагаться на такой порядок было нельзя.

Новые ключевые слова: async и await

Корутины с async и await были введены в Python 3.5, однако для обратной совмесимости всё ещё можно было объявить переменные с такими именами. Теперь это будет выдавать ошибку.

>>> async = 1
  File "", line 1
    async = 1
          ^
SyntaxError: invalid syntax

>>> def await():
  File "", line 1
    def await():
            ^
SyntaxError: invalid syntax

Улучшение модуля asyncio

Модуль asyncio для поддержки асинхронности был представлен в Python 3.4 (введение). В Python 3.7 asyncio получил большое количество новых функций, поддержку контекстных переменных и улучшения производительности. Например, используя asyncio.run(), вы можете легко вызывать корутины из синхронного кода, не создавая event loop.

import asyncio

async def hello_world():
    print("Hello World!")

asyncio.run(hello_world())

PEP 567, Контекстные переменные

Контекстные переменные — это переменные, которые могут иметь различные значения в зависимости от окружения. Они похожи на Thread-Local Storage, в которых каждый исполняемый тред может иметь разное значение переменной, однако контекстные переменные могут иметь разные значение даже в пределах одного треда. Основная область применения — параллельные асинхронные задачи.

import contextvars

name = contextvars.ContextVar("name")
contexts = list()

def greet():
    print(f"Hello {name.get()}")

# Construct contexts and set the context variable name
for first_name in ["Steve", "Dina", "Harry"]:
    ctx = contextvars.copy_context()
    ctx.run(name.set, first_name)
    contexts.append(ctx)

# Run greet function inside each context
for ctx in reversed(contexts):
    ctx.run(greet)

Запуск скрипта приветствует Steve, Dina, и Harry в обратном порядке:

$ python3.7 context_demo.py
Hello Harry
Hello Dina
Hello Steve

Импорт файлов с данными с помощью importlib.resources

Как происходила упаковка ресурсов в пакет до Python 3.7? Обычно выбирали один из трёх способов

  • Захардкоженные пути к ресурсам
  • Поместить файлы внурь пакета и получать к ним доступ с помощью __file__
  • Использовать setuptools.pkg_resources

Первый способ не портируем. Второй подходит лучше, но если Python-пакет находится внутрь zip архива, то там не будет атрибута __file__, что создает проблемы. Третий способ работает, однако слишком медленный.

Теперь появляется ещё один способ: новый модуль importlib.resources в стандартной библиотеке. Он использует уже существующую функциональность импорта модулей для загрузки файлов. Допустим, у вас есть ресурсы внутри пакета:

data/
│
├── alice_in_wonderland.txt
└── __init__.py

Теперь вы можете получить доступ к alice_in_wonderland.txt следующим образом:

>>> from importlib import resources
>>> with resources.open_text("data", "alice_in_wonderland.txt") as fid:
...     alice = fid.readlines()
... 
>>> print("".join(alice[:7]))

CHAPTER I. Down the Rabbit-Hole

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?’

Похожая функция resources.open_binary() открывает файлы в бинарном режиме.

Оптимизации

Ни один релиз Python не обходится без набора оптимизаций. Python 3.7 не стал исключением:

  • Снижены накладные расходы при вызове многих методов из стандартной библиотеки
  • В целом методы теперь вызываются на 20% быстрее
  • Время запуска самого Python снижено на 10–30%
  • Импортирование typing теперь быстрее в 7 раз.

Различные лучшения CPython

  • Уход от ASCII как от дефолтной кодировки:
  • PEP 552, Воспроизводимые .pycs
  • Новый опция -X
    $ python3.7 -X importtime my_script.py
    import time: self [us] | cumulative | imported package
    import time:      2607 |       2607 | _frozen_importlib_external
    ...
    import time:       844 |      28866 |   importlib.resources
    import time:       404 |      30434 | plugins
    
    
    Вы также можете использовать -X dev для активации «режима разработки» и -X utf8 для активации режима UTF-8. Полный список опций.
  • PEP 565, улучшенная обработка DeprecationWarning

>>> Новость на Real Python

>>> Официальный обзор изменений

©  Linux.org.ru