Интересные новые функции в Python 3.7

Интересные новые функции в Python 3.7

Python 3.7 выпущен officially! Эта новая версия Python находится в разработке с September 2016, и теперь мы все получаем удовольствие от результатов напряженной работы основных разработчиков.

Что принесет новая версия Python? Хотя documentation дает хороший обзор новых функций, в этой статье мы подробно рассмотрим некоторые из самых важных новостей. Они включают:

  • Упрощенный доступ к отладчикам благодаря новой встроенной функции + breakpoint () +

  • Простое создание классов с использованием классов данных

  • Индивидуальный доступ к атрибутам модуля

  • Улучшена поддержка подсказок типа

  • Более точные функции синхронизации

Что еще более важно, Python 3.7 работает быстро.

В последних разделах этой статьи вы узнаете больше об этой скорости, а также о некоторых других интересных функциях Python 3.7. Вы также получите несколько советов по обновлению до новой версии.

+ Точка останова () + Встроенная

Хотя мы могли бы стремиться написать идеальный код, простая истина в том, что мы никогда этого не делаем. Отладка является важной частью программирования. Python 3.7 представляет новую встроенную функцию + breakpoint () +. Это на самом деле не добавляет никакой новой функциональности в Python, но делает использование отладчиков более гибким и интуитивно понятным.

Предположим, что у вас есть следующий код ошибки в файле + bugs.py +:

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

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

Запуск кода вызывает + ZeroDivisionError + внутри функции + div () +. Допустим, вы хотите прервать ваш код и перейти на debugger прямо вверху +div () +. Вы можете сделать это, установив в своем коде так называемую «точку останова»:

def divide(e, f):
    # Insert breakpoint here
    return f/e

Точка останова - это сигнал внутри вашего кода, что выполнение должно временно остановиться, чтобы вы могли посмотреть на текущее состояние программы. Как вы устанавливаете точку останова? В Python 3.6 и ниже вы используете эту несколько загадочную строку:

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

Здесь https://docs.python.org/library/pdb.html [+ pdb +] - это отладчик Python из стандартной библиотеки. В Python 3.7 вместо этого вы можете использовать новый вызов функции + breakpoint () + в качестве ярлыка:

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

В фоновом режиме + breakpoint () + сначала импортирует + pdb +, а затем вызывает + pdb.set_trace () + для вас. Очевидные преимущества заключаются в том, что + breakpoint () + легче запомнить, и вам нужно всего лишь набрать 12 символов вместо 27. Однако реальным преимуществом использования + breakpoint () + является его настраиваемость.

Запустите ваш скрипт + bugs.py + с помощью + breakpoint () +:

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

Скрипт прервется, когда он достигнет + breakpoint () +, и отправит вас в сеанс отладки PDB. Вы можете набрать + c + и нажать [.keys] # Enter #, чтобы продолжить скрипт. Обратитесь к Nathan Jennings 'Руководство по PDB, если вы хотите узнать больше о PDB и отладке.

Теперь скажите, что вы думаете, что исправили ошибку. Вы хотели бы снова запустить скрипт, но не останавливаясь в отладчике. Конечно, вы можете закомментировать строку + breakpoint () +, но другой вариант - использовать переменную окружения + PYTHONBREAKPOINT +. Эта переменная управляет поведением + breakpoint () +, а установка + PYTHONBREAKPOINT = 0 + означает, что любой вызов + breakpoint () + игнорируется:

$ PYTHONBREAKPOINT=0 python3.7 bugs.py
ZeroDivisionError: division by zero

К сожалению, похоже, что вы все-таки не исправили ошибку …​

Другой вариант - использовать + PYTHONBREAKPOINT +, чтобы указать отладчик, отличный от PDB. Например, чтобы использовать PuDB (визуальный отладчик в консоли), вы можете сделать:

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

Чтобы это работало, вам нужно установить + pudb + (+ pip install pudb +). Python позаботится об импорте + pudb + для вас. Таким образом, вы также можете установить отладчик по умолчанию. Просто установите переменную окружения + PYTHONBREAKPOINT + в выбранный вами отладчик. См. Https://www.schrodinger.com/kb/1842[this guide] для получения инструкций о том, как установить переменную среды в вашей системе.

Новая функция + breakpoint () + работает не только с отладчиками. Одним из удобных вариантов может быть просто запустить интерактивную оболочку внутри вашего кода. Например, чтобы начать сеанс 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 () +. Следующий код печатает все переменные в локальной области видимости. Добавьте его в файл с именем + bp_utils.py +:

from pprint import pprint
import sys

def print_locals():
    caller = sys._getframe(1)  # Caller is 1 frame up.
    pprint(caller.f_locals)

Чтобы использовать эту функцию, установите + PYTHONBREAKPOINT +, как и прежде, с помощью обозначения + <module>. <Function> +:

$ PYTHONBREAKPOINT=bp_utils.print_locals python3.7 bugs.py
{'e': 0, 'f': 1}
ZeroDivisionError: division by zero

Обычно + breakpoint () + будет использоваться для вызова функций и методов, которые не нуждаются в аргументах. Тем не менее, можно также передавать аргументы. Измените строку + breakpoint () + в + bugs.py + на:

breakpoint(e, f, end="<-END\n")
*Примечание:* Отладчик PDB по умолчанию вызовет `+ TypeError +` в этой строке, потому что `+ pdb.set_trace () +` не принимает никаких позиционных аргументов.

Запустите этот код с + breakpoint () +, маскирующимся под функцию + print () +, чтобы увидеть простой пример передаваемых аргументов:

$ PYTHONBREAKPOINT=print python3.7 bugs.py
0 1<-END
ZeroDivisionError: division by zero

См. Https://www.python.org/dev/peps/pep-0553/[PEP 553], а также документацию для https://docs.python.org/3.7/library/functions.html#breakpoint [` + breakpoint () + ] и https://docs.python.org/3.7/library/sys.html#sys.breakpointhook [ + sys.breakpointhook () + `] для получения дополнительной информации.

Классы данных

Новый модуль + dataclasses + делает более удобным написание собственных классов, таких как специальные методы, такие как + . init () +, + . repr () + `и + . eq () + добавляются автоматически. Используя декоратор `+ @ dataclass +, вы можете написать что-то вроде:

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

Эти девять строк кода представляют собой довольно много стандартного кода и передового опыта. Подумайте, что нужно для реализации + Country + как обычного класса: метод + . init () +, + repr +, шесть различных методов сравнения, а также + .beach_per_person () + метод. Вы можете развернуть поле ниже, чтобы увидеть реализацию + Country +, которая примерно эквивалентна классу данных:

После создания класс данных является нормальным классом. Например, вы можете наследовать от класса данных обычным способом. Основная цель классов данных состоит в том, чтобы быстро и легко написать надежные классы, в частности небольшие классы, которые в основном хранят данные.

Вы можете использовать класс данных + Country +, как и любой другой класс:

>>>

>>> norway = Country("Norway", 5320045, 323802, 58133)
>>> norway
Country(name='Norway', population=5320045, coastline=58133)

>>> norway.area
323802

>>> usa = Country("United States", 326625791, 9833517, 19924)
>>> nepal = Country("Nepal", 29384297, 147181)
>>> nepal
Country(name='Nepal', population=29384297, coastline=0)

>>> usa.beach_per_person()
0.06099946957342386

>>> norway.beach_per_person()
10.927163210085629

Обратите внимание, что все поля + .name +, + .population +, + .area + и + .coastline + используются при инициализации класса (хотя + .coastline + является необязательным, как показано в пример не имеющего выхода к морю Непала). Класс + Country + имеет разумный https://dbader.org/blog/python-repr-vs-str [+ repr +], в то время как определение методов работает так же, как и для обычных классов.

По умолчанию классы данных можно сравнивать на равенство. Поскольку мы указали + order = True + в декораторе + @ dataclass +, класс + Country + также можно отсортировать:

>>>

>>> norway == norway
True

>>> nepal == usa
False

>>> sorted((norway, usa, nepal))
[Country(name='Nepal', population=29384297, coastline=0),
 Country(name='Norway', population=5320045, coastline=58133),
 Country(name='United States', population=326625791, coastline=19924)]

Сортировка происходит по значениям поля, сначала + .name +, затем + .population + и так далее. Однако, если вы используете + field () +, вы можете customize какие поля будут использоваться при сравнении. В этом примере поле + .area + было исключено из + repr + и сравнений.

*Примечание:* Данные по стране взяты из https://www.cia.gov/library/publications/the-world-factbook/[CIA World Factbook] с оценкой численности населения на июль 2017 года.

Прежде чем все вы закажете свой следующий пляжный отдых в Норвегии, вот что говорится в сборнике фактов о Norwegian климата: «Умеренный вдоль побережья, измененный североатлантическим течением; более холодный интерьер с повышенным количеством осадков и более холодным летом; Дождливый год на западном побережье.

Классы данных делают некоторые из тех же вещей, что и https://dbader.org/blog/writing-clean-python-with-namedtuples [+ namedtuple +]. Тем не менее, они черпают свое самое большое вдохновение из проекта + attrs +. См. Наш full справочник по классам данных для получения дополнительных примеров и дополнительной информации, а также PEP 557 для официального описания.

Настройка атрибутов модуля

Атрибуты везде в Python! В то время как атрибуты класса, вероятно, являются самыми известными, атрибуты на самом деле могут быть добавлены практически к чему угодно, включая функции и модули. Некоторые из основных функций Python реализованы в виде атрибутов: большая часть функций самоанализа, строки документов и пространства имен. Функции внутри модуля доступны как атрибуты модуля.

Атрибуты чаще всего извлекаются с использованием точечной нотации: + thing.attribute +. Однако вы также можете получить атрибуты с именами во время выполнения, используя + getattr () +:

import random

random_attr = random.choice(("gammavariate", "lognormvariate", "normalvariate"))
random_func = getattr(random, random_attr)

print(f"A {random_attr} random value: {random_func(1, 1)}")

Выполнение этого кода даст что-то вроде:

A gammavariate random value: 2.8017715125270618

Для классов вызов + thing.attr + сначала будет искать + attr +, определенный для + thing +. Если он не найден, то вызывается специальный метод + thing . getattr (" attr ") +. (Это упрощение. См. Http://blog.lerner.co.il/python-attributes/[this article] для получения более подробной информации.) Метод + . getattr () + можно использовать для настройки доступа к атрибутам объектов.

До Python 3.7 такая же настройка не была легко доступна для атрибутов модуля. Однако PEP 562 представляет + getattr () + для модулей вместе с соответствующей функцией + dir () +. Специальная функция + dir () + позволяет настроить результат вызова https://realpython.com/python-modules-packages/#the-dir-function [+ dir () + для модуля].

Сам PEP дает несколько примеров того, как эти функции могут использоваться, включая добавление предупреждений об устаревании в функции и отложенную загрузку тяжелых подмодулей. Ниже мы создадим простую систему плагинов, которая позволяет динамически добавлять функции в модуль. Этот пример использует преимущества пакетов Python. См. Https://realpython.com/python-modules-packages/[this article], если вам нужно обновить пакеты.

Создайте новый каталог + plugins + и добавьте следующий код в файл + plugins/ init . Py +:

from importlib import import_module
from importlib import resources

PLUGINS = dict()

def register_plugin(func):
    """Decorator to register plug-ins"""
    name = func.__name__
    PLUGINS[name] = func
    return func

def __getattr__(name):
    """Return a named plugin"""
    try:
        return PLUGINS[name]
    except KeyError:
        _import_plugins()
        if name in PLUGINS:
            return PLUGINS[name]
        else:
            raise AttributeError(
                f"module {__name__!r} has no attribute {name!r}"
            ) from None

def __dir__():
    """List available plug-ins"""
    _import_plugins()
    return list(PLUGINS.keys())

def _import_plugins():
    """Import all resources to register plug-ins"""
    for name in resources.contents(__name__):
        if name.endswith(".py"):
            import_module(f"{__name__}.{name[:-3]}")

Прежде чем мы рассмотрим, что делает этот код, добавьте еще два файла в каталог + plugins +. Во-первых, давайте посмотрим + plugins/plugin_1.py +:

from . import register_plugin

@register_plugin
def hello_1():
    print("Hello from Plugin 1")

Затем добавьте аналогичный код в файл + plugins/plugin_2.py +:

from . import register_plugin

@register_plugin
def hello_2():
    print("Hello from Plugin 2")

@register_plugin
def goodbye():
    print("Plugin 2 says goodbye")

Эти плагины теперь можно использовать следующим образом:

>>>

>>> import plugins
>>> plugins.hello_1()
Hello from Plugin 1

>>> dir(plugins)
['goodbye', 'hello_1', 'hello_2']

>>> plugins.goodbye()
Plugin 2 says goodbye

Это может показаться не таким уж революционным (и, вероятно, это не так), но давайте посмотрим, что на самом деле здесь произошло. Обычно, чтобы иметь возможность вызывать + plugins.hello_1 () +, функция + hello_1 () + должна быть определена в модуле + plugins + или явно импортирована внутри + init . Py + в `+ Плагины + `пакет. Здесь это не так!

Вместо этого + hello_1 () + определяется в произвольном файле внутри пакета + plugins +, а + hello_1 () + становится частью пакета + plugins +, регистрируя себя с помощью `+ @ register_plugin + decorator.

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

Давайте кратко рассмотрим, что + getattr () + делает внутри кода + plugins/ init . Py +. Когда вы запросили + plugins.hello_1 () +, Python сначала ищет функцию + hello_1 () + внутри файла + plugins/ init . Py +. Так как такой функции не существует, Python вместо этого вызывает + getattr (" hello_1 ") +. Запомните исходный код функции + getattr () +:

def __getattr__(name):
    """Return a named plugin"""
    try:
        return PLUGINS[name]        # 1) Try to return plugin
    except KeyError:
        _import_plugins()           # 2) Import all plugins
        if name in PLUGINS:
            return PLUGINS[name]    # 3) Try to return plugin again
        else:
            raise AttributeError(   # 4) Raise error
                f"module {__name__!r} has no attribute {name!r}"
            ) from None

+ getattr () + содержит следующие шаги. Числа в следующем списке соответствуют пронумерованным комментариям в коде:

  1. Во-первых, функция оптимистично пытается вернуть именованный плагин из словаря + PLUGINS +. Это будет успешным, если плагин с именем + name + существует и уже был импортирован.

  2. Если названный плагин не найден в словаре + PLUGINS +, мы удостоверимся, что все плагины импортированы.

  3. Верните названный плагин, если он стал доступен после импорта.

  4. Если плагин отсутствует в словаре + PLUGINS + после импорта всех плагинов, мы вызываем + AttributeError +, говоря, что + name + не является атрибутом (плагином) в текущем модуле.

Как заполнен словарь + PLUGINS +? Функция + _import_plugins () + импортирует все файлы Python внутри пакета + plugins +, но, похоже, не касается + PLUGINS +:

def _import_plugins():
    """Import all resources to register plug-ins"""
    for name in resources.contents(__name__):
        if name.endswith(".py"):
            import_module(f"{__name__}.{name[:-3]}")

Не забывайте, что каждая функция плагина украшена декоратором + @ register_plugin +. Этот декоратор вызывается при импорте плагинов и фактически заполняет словарь + PLUGINS +. Вы можете увидеть это, если вручную импортируете один из файлов плагинов:

>>>

>>> import plugins
>>> plugins.PLUGINS
{}

>>> import plugins.plugin_1
>>> plugins.PLUGINS
{'hello_1': <function hello_1 at 0x7f29d4341598>}

Продолжая пример, обратите внимание, что вызов + dir () + для модуля также импортирует остальные плагины:

>>>

>>> dir(plugins)
['goodbye', 'hello_1', 'hello_2']

>>> plugins.PLUGINS
{'hello_1': <function hello_1 at 0x7f29d4341598>,
 'hello_2': <function hello_2 at 0x7f29d4341620>,
 'goodbye': <function goodbye at 0x7f29d43416a8>}

+ dir () + обычно перечисляет все доступные атрибуты объекта. Обычно использование + dir () + в модуле приводит к чему-то вроде этого:

>>>

>>> import plugins
>>> dir(plugins)
['PLUGINS', '__builtins__', '__cached__', '__doc__',
 '__file__', '__getattr__', '__loader__', '__name__',
 '__package__', '__path__', '__spec__', '_import_plugins',
 'import_module', 'register_plugin', 'resources']

Хотя это может быть полезной информацией, мы больше заинтересованы в раскрытии доступных плагинов. В Python 3.7 вы можете настроить результат вызова + dir () + для модуля, добавив специальную функцию + dir () +. Для + plugins/ init . Py + эта функция сначала проверяет, все ли плагины были импортированы, а затем перечисляет их имена:

def __dir__():
    """List available plug-ins"""
    _import_plugins()
    return list(PLUGINS.keys())

Прежде чем покинуть этот пример, обратите внимание, что мы также использовали еще одну интересную новую функцию Python 3.7. Чтобы импортировать все модули внутри каталога + plugins +, мы использовали новый модуль https://docs.python.org/3.7/library/importlib.html#module-importlib.resources [+ importlib.resources +]. Этот модуль предоставляет доступ к файлам и ресурсам внутри модулей и пакетов без необходимости взломов + file + (которые не всегда работают) или + pkg_resources + (что медленно). Другие функции + importlib.resources + будут ссылками: # other-pretty-cool-features [выделено позже].

Улучшения печати

Type подсказки и аннотации находились в постоянной разработке на протяжении всей серии выпусков Python 3. Система ввода Python теперь достаточно стабильна. Тем не менее, Python 3.7 вносит некоторые улучшения в таблицу: лучшая производительность, поддержка ядра и прямые ссылки.

Python не выполняет никакой проверки типов во время выполнения (если вы явно не используете пакеты, такие как + принудительное + +). Поэтому добавление подсказок типа в ваш код не должно влиять на его производительность.

К сожалению, это не совсем так, поскольку большинству подсказок по типу нужен модуль + typing +. Модуль + typing + является одним из slowest модулей в стандартной библиотеке. PEP 560 добавляет поддержку ядра для ввода текста в Python 3.7, что значительно ускоряет модуль + typing +. Подробности этого, как правило, не нужно знать. Просто откиньтесь назад и наслаждайтесь повышенной производительностью.

Хотя система типов Python достаточно выразительна, одной из проблем, вызывающих некоторые затруднения, являются прямые ссылки. Подсказки типов - или, в более общем случае, аннотации - оцениваются во время импорта модуля. Следовательно, все имена должны быть определены до того, как они будут использованы. Следующее невозможно:

class Tree:
    def __init__(self, left: Tree, right: Tree) -> None:
        self.left = left
        self.right = right

Выполнение кода вызывает + NameError +, потому что класс + Tree + еще не (полностью) определен в определении метода + . init () +:

Traceback (most recent call last):
  File "tree.py", line 1, in <module>
    class Tree:
  File "tree.py", line 2, in Tree
    def __init__(self, left: Tree, right: Tree) -> None:
NameError: name 'Tree' is not defined

Чтобы преодолеть это, вам нужно было бы написать " Tree " вместо строкового литерала:

class Tree:
    def __init__(self, left: "Tree", right: "Tree") -> None:
        self.left = left
        self.right = right

См. Https://www.python.org/dev/peps/pep-0484/#forward-references[PEP 484] для исходного обсуждения.

В будущем Python 4.0 такие так называемые прямые ссылки будут разрешены. Это будет обработано, не оценивая аннотации, пока это явно не запрошено. PEP 563 описывает детали этого предложения. В Python 3.7 прямые ссылки уже доступны как https://docs.python.org/library/future.html [+ future + import]. Теперь вы можете написать следующее:

from __future__ import annotations

class Tree:
    def __init__(self, left: Tree, right: Tree) -> None:
        self.left = left
        self.right = right

Обратите внимание, что помимо избежания несколько неуклюжего синтаксиса " Tree " отложенная оценка аннотаций также ускорит ваш код, поскольку подсказки типов не выполняются. Прямые ссылки уже поддерживаются + mypy +.

Безусловно, наиболее распространенное использование аннотаций - хинтинг типов. Тем не менее, вы имеете полный доступ к аннотациям во время выполнения и можете использовать их по своему усмотрению. Если вы обрабатываете аннотации напрямую, вам необходимо явно разобраться с возможными форвардными ссылками.

Давайте создадим несколько заведомо глупых примеров, которые показывают, когда аннотации оцениваются. Сначала мы делаем это в старом стиле, поэтому аннотации оцениваются во время импорта. Пусть + anno.py + содержит следующий код:

def greet(name: print("Now!")):
    print(f"Hello {name}")

Обратите внимание, что аннотация + name + есть + print () +. Это только для того, чтобы точно увидеть, когда аннотация оценивается. Импортируйте новый модуль:

>>>

>>> import anno
Now!

>>> anno.greet.__annotations__
{'name': None}

>>> anno.greet("Alice")
Hello Alice

Как видите, аннотация была оценена во время импорта. Обратите внимание, что + name + заканчивается аннотированием + None +, потому что это возвращаемое значение + print () +.

Добавьте импорт + uture +, чтобы включить отложенную оценку аннотаций:

from __future__ import annotations

def greet(name: print("Now!")):
    print(f"Hello {name}")

Импорт этого обновленного кода не оценивает аннотацию:

>>>

>>> import anno

>>> anno.greet.__annotations__
{'name': "print('Now!')"}

>>> anno.greet("Marty")
Hello Marty

Обратите внимание, что + Now! + Никогда не печатается, и аннотация сохраняется в виде строкового литерала в словаре + annotations +. Чтобы оценить аннотацию, используйте + typing.get_type_hints () + или + eval () +:

>>>

>>> import typing
>>> typing.get_type_hints(anno.greet)
Now!
{'name': <class 'NoneType'>}

>>> eval(anno.greet.__annotations__["name"])
Now!

>>> anno.greet.__annotations__
{'name': "print('Now!')"}

Заметьте, что словарь + annotations + никогда не обновляется, поэтому вам нужно оценивать аннотацию каждый раз, когда вы ее используете.

Точность времени

В Python 3.7 модуль https://docs.python.org/library/time.html [+ time +] получает некоторые новые функции, как описано в https://www.python.org/dev/peps/pep-. 0564/[ОПТОСОЗ 564]. В частности, добавлены следующие шесть функций:

  • + clock_gettime_ns () +: Возвращает время указанных часов

  • + clock_settime_ns () +: Устанавливает время указанных часов

  • + monotonic_ns () +: Возвращает время относительных часов, которые не могут вернуться назад (например, из-за перехода на летнее время)

  • + perf_counter_ns () + : возвращает значение счетчика производительности - часов, специально предназначенных для измерения коротких интервалов

  • + process_time_ns () +: Возвращает сумму системного и пользовательского процессорного времени текущего процесса (не включая время ожидания)

  • + time_ns () + : возвращает количество наносекунд с 1 января 1970 г.

В некотором смысле, никакой новой функциональности не добавлено. Каждая функция похожа на уже существующую функцию без суффикса + _ns +. Разница в том, что новые функции возвращают количество наносекунд как + int + вместо количества секунд как + float +.

Для большинства приложений разница между этими новыми функциями наносекунды и их старым аналогом не будет заметной. Однако о новых функциях легче рассуждать, поскольку они полагаются на + int + вместо + float +. Числа с плавающей запятой являются by nature inaccurate:

>>>

>>> 0.1 + 0.1 + 0.1
0.30000000000000004

>>> 0.1 + 0.1 + 0.1 == 0.3
False

Это не проблема Python, а скорее следствие того, что компьютерам необходимо представлять бесконечные десятичные числа с использованием конечного числа битов.

Python + float + следует стандарту IEEE 754 и использует 53 значащих бита. В результате любое время, превышающее 104 дня (примерно 2/3 или приблизительно 9 квадриллион наносекунд), не может быть выражено как число с плавающей точкой с точностью до наносекунды. Напротив, Python https://stackoverflow.com/a/9860611 [+ int + не ограничен], поэтому целое число наносекунд всегда будет иметь точность наносекунды независимо от значения времени.

Например, + time.time () + возвращает количество секунд с 1 января 1970 года. Это число уже довольно большое, поэтому точность этого числа находится на микросекундном уровне. Эта функция демонстрирует наибольшее улучшение в версии + _ns +. Разрешение + time.time_ns () + примерно в 3 раза лучше, чем для + time.time () + ,

Кстати, что такое наносекунда? Технически, это одна миллиардная доля секунды, или + 1e-9 + секунда, если вы предпочитаете научную запись. Это просто цифры, которые не дают никакой интуиции. Для лучшей визуальной помощи см. Https://en.wikipedia.org/wiki/Grace_Hopper#Anecdotes[Grace Hopper’s] замечательный demonstration of наносекундный.

Кроме того, если вам нужно работать с datetime с точностью до наносекунды, стандартная библиотека + datetime + ее не обрезает. Он явно обрабатывает только микросекунды:

>>>

>>> from datetime import datetime, timedelta
>>> datetime(2018, 6, 27) + timedelta(seconds=1e-6)
datetime.datetime(2018, 6, 27, 0, 0, 0, 1)

>>> datetime(2018, 6, 27) + timedelta(seconds=1e-9)
datetime.datetime(2018, 6, 27, 0, 0)

Вместо этого вы можете использовать + astropy + project. Его пакет + astropy.time + представляет даты и время, использующие два объекта + float +, которые гарантируют «точность до наносекунды в течение времен, охватывающих возраст вселенной «.

>>>

>>> from astropy.time import Time, TimeDelta
>>> Time("2018-06-27")
<Time object: scale='utc' format='iso' value=2018-06-27 00:00:00.000>

>>> t = Time("2018-06-27") + TimeDelta(1e-9, format="sec")
>>> (t - Time("2018-06-27")).sec
9.976020010071807e-10

Последняя версия + astropy + доступна в Python 3.5 и более поздних версиях.

Другие довольно интересные функции

До сих пор вы видели заголовки новостей о том, что нового в Python 3.7. Тем не менее, есть много других изменений, которые тоже довольно крутые. В этом разделе мы кратко рассмотрим некоторые из них.

Заказ словарей гарантирован

Реализация Python 3.6 на CPython имеет упорядоченные словари. (PyPy также имеет это.) Это означает, что элементы в словарях повторяются в том же порядке, в котором они были вставлены. Первый пример использует Python 3.5, а второй - Python 3.6:

>>>

>>> {"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}

В Python 3.6 этот порядок был просто хорошим следствием реализации + dict +. В Python 3.7, однако, словари, сохраняющие порядок вставки, являются частью спецификации https://mail.python.org/pipermail/python-dev/2017-De December/151283.html[language]. Таким образом, теперь на него можно положиться в проектах, которые поддерживают только Python> = 3.7 (или CPython> = 3.6).

«` + Async + » и « + await + `» являются ключевыми словами

В Python 3.5 введен coroutines с синтаксисом + async + и + await +. Чтобы избежать проблем обратной совместимости, + async + и + await + не были добавлены в список зарезервированных ключевых слов. Другими словами, все еще можно было определить переменные или функции с именами + async + и + await +.

В Python 3.7 это больше невозможно:

>>>

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

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

«` + Asyncio + `» Подтяжка лица

Стандартная библиотека + asyncio + изначально была представлена ​​в Python 3.4 для управления параллелизмом современным способом с использованием циклов событий, сопрограмм и фьючерсов. Вот gentle введение.

В Python 3.7 модуль + asyncio + получает major подтяжку лица, включая множество новых функций, поддержку контекстных переменных (см. ссылка: # контекстные переменные [ниже]) и улучшения производительности. Особо следует отметить + asyncio.run () +, который упрощает вызов сопрограмм из синхронного кода. Используя https://docs.python.org/3.7/library/asyncio-task.html#asyncio.run [+ asyncio.run () +], вам не нужно явно создавать цикл событий. Теперь можно написать асинхронную программу Hello World:

import asyncio

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

asyncio.run(hello_world())

Контекстные переменные

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

В следующем примере создаются три контекста, каждый со своим собственным значением для значения + name +. Функция + greet () + позже может использовать значение + name + внутри каждого контекста:

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)

Запуск этого сценария встречает Стива, Дину и Гарри в обратном порядке:

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

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

Одна из проблем при упаковке проекта Python - это решить, что делать с ресурсами проекта, такими как файлы данных, необходимые для проекта. Обычно используются несколько вариантов:

  • Жесткий код пути к файлу данных.

  • Поместите файл данных в пакет и найдите его, используя + file +.

  • Используйте https://setuptools.readthedocs.io/en/latest/pkg_resources.html [+ setuptools.pkg_resources +] для доступа к ресурсу файла данных.

У каждого из них есть свои недостатки. Первый вариант не переносимый. Использование + file + более переносимо, но если проект Python установлен, он может оказаться внутри zip и не иметь атрибута + file +. Третий вариант решает эту проблему, но, к сожалению, очень медленный.

Лучшим решением является новый модуль https://docs.python.org/3.7/library/importlib.html#module-importlib.resources [+ importlib.resources +] в стандартной библиотеке. Он использует существующие функции импорта Python для импорта файлов данных. Предположим, у вас есть ресурс внутри пакета Python:

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

Обратите внимание, что + data + должен быть Python package. То есть каталог должен содержать файл + 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?"

Аналогичная https://docs.python.org/3.7/library/importlib.html#importlib.resources.open_binary [+ resources.open_binary () +] функция позволяет открывать файлы в двоичном режиме. В более ранней ссылке: # customization-of-module-attribute [пример «плагины как атрибуты модуля»], мы использовали + importlib.resources +, чтобы обнаружить доступные плагины, используя + resources.contents () +. См. Https://www.youtube.com/watch?v=ZsGFU2qh73E[Barry Warsaw’s PyCon 2018 talk] для получения дополнительной информации.

Можно использовать + importlib.resources + в Python 2.7 и Python 3.4+ через backport. Доступен guide по миграции с + pkg_resources + на + importlib.resources +.

Хитрости разработчика

В Python 3.7 добавлено несколько функций, нацеленных на вас как на разработчика. У вас есть ссылка: # the-breakpoint-built-in [уже видели новую + breakpoint () + встроенная]. Кроме того, несколько новых https://docs.python.org/3.7/using/cmdline.html#id5 [+ -X + параметры командной строки] были добавлены в интерпретатор Python.

Вы можете легко получить представление о том, сколько времени занимает импорт в вашем скрипте, используя + -X importtime +:

$ 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

Столбец + cumulative + показывает совокупное время импорта (в микросекундах). В этом примере импорт + plugins + занял около 0,03 секунды, большая часть которого была потрачена на импорт + importlib.resources +. Столбец + self + показывает время импорта без учета вложенного импорта.

Теперь вы можете использовать + -X dev +, чтобы активировать “режим разработки”. Режим разработки добавит некоторые функции отладки и проверки во время выполнения, которые считаются слишком медленными для включения по умолчанию. К ним относится включение https://docs.python.org/library/faulthandler.html#module-faulthandler [+ faulthandler +] для отображения трассировки при серьезных сбоях, а также дополнительных предупреждений и отладочных хуков.

Наконец, + -X utf8 + включает режим UTF-8. (См. Https://www.python.org/dev/peps/pep-0540/[PEP 540].) В этом режиме + UTF-8 + будет использоваться для кодирования текста независимо от текущей локали.

Оптимизации

Каждый новый выпуск Python поставляется с набором оптимизаций. В Python 3.7 есть несколько существенных ускорений, включая:

  • Вызывает меньше затрат при вызове многих методов в стандартной библиотеке.

  • Вызовы методов в целом быстрее на 20%.

  • Время запуска самого Python сокращается на 10-30%.

  • Импорт + typing + выполняется в 7 раз быстрее.

Кроме того, многие другие специализированные оптимизации включены. См. Https://docs.python.org/3.7/whatsnew/3.7.html#optimizations[this list] для подробного обзора.

Результатом всех этих оптимизаций является то, что Python 3.7 работает быстро. Это просто fastest версия CPython, выпущенная до настоящего времени.

Итак, я должен обновить?

Давайте начнем с простого ответа. Если вы хотите опробовать какие-либо новые функции, которые вы видели здесь, вам нужно иметь возможность использовать Python 3.7. Использование таких инструментов, как https://github.com/pyenv/pyenv [+ pyenv +] или Anaconda упрощает установку нескольких версий Python рядом друг с другом. , У установки и тестирования Python 3.7 нет недостатков.

Теперь для более сложных вопросов. Следует ли обновить производственную среду до Python 3.7? Должны ли вы сделать свой собственный проект зависимым от Python 3.7, чтобы воспользоваться новыми возможностями?

С очевидным предостережением, что вы всегда должны проводить тщательное тестирование перед обновлением производственной среды, в Python 3.7 есть очень мало вещей, которые сломают предыдущий код (хотя + async + и + await + становятся ключевыми словами). Если вы уже используете современный Python, обновление до 3.7 должно быть достаточно плавным. Если вы хотите быть немного консервативным, возможно, вы захотите дождаться выпуска первой версии поддержки - Python 3.7.1 - https://www.python.org/dev/peps/pep-0537/#maintenance-releases [ориентировочно ожидается некоторое время в июле 2018 года].

Утверждать, что вы должны сделать свой проект 3.7 сложнее. Многие из новых функций в Python 3.7 доступны либо как обратные порты для Python 3.6 (классы данных, + importlib.resources +), так и для удобства (более быстрый запуск и вызовы методов, упрощенная отладка и опции + -X +). Последнее, вы можете воспользоваться преимуществами, запустив Python 3.7 самостоятельно, сохраняя свой код совместимым с Python 3.6 (или ниже).

Основными функциями, которые блокируют ваш код на Python 3.7, являются: ссылка: # customization-of-module-attribute [+ getattr () + для модулей], ссылка: # typing-extensionments [прямые ссылки в подсказках типов] и ссылка: # временная точность [наносекунды + время + функции]. Если вам действительно нужно что-то из этого, вы должны пойти дальше и поднять свои требования. В противном случае ваш проект, вероятно, будет более полезным для других, если его можно будет запускать на Python 3.6 еще какое-то время.

См. Https://docs.python.org/3.7/whatsnew/3.7.html#porting-to-python-37[Porting to Python 3.7 руководство], чтобы узнать подробности при обновлении.