Python 3.7
Спустя полтора года после выхода предыдущей мажорной версии, наконец-то состоялся релиз 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
$ 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
>>> Официальный обзор изменений