
Beautiful is better than ugly
“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.” — John Wood (possibly)
“Всегда пишите код так, будто в дальнейшем поддерживать его будет злобный психопат, который знает где ты живешь.” — (Мой нехудожественный перевод).
Python — это язык программирования, уделяющий много внимания тому, как мы пишем код. Самый первый пункт Zen of Python, принципов разработки на Python от его BDFL: “Beautiful is better than ugly”. Красивое лучше уродливого. Это само по себе простое и понятное утверждение, вынесенное на первое место в дзэне, напоминает нам простую истину — мы пишем код для людей, а не для машин. Машине для исполнения программы хватит нулей и единиц в бинарном файле, человек же куда более требователен.
Именно поэтому в разработке кода мы стараемся использовать все доступные средства, для того чтобы сделать его удобным для чтения и понятным человеку. В Python множество инструментов, которые могут помочь улучшить читаемость кода, и Context manager, о котором дальше пойдет речь, один из них.
Контекст
Прежде чем перейти к менеджеру контекста, вспомним что такое сам контекст.
# Запись в файл без использования менеджера контекста file = open("demo_1.txt", "w") try: file.write("1!") finally: # Убеждаемся что первый Файл был закрыт. file.close() file = open("demo_2.txt", "w") try: file.write("2!") finally: # Убеждаемся что второй Файл был закрыт. file.close()
Блоки повторяющегося кода для работы с файлами demo_1.txt и demo_2.txt могут быть разбиты на отдельные шаги:
# Вход file = open("demo_1.txt", "w")
# Собственно работа с файлом file.write("1!")
# Выход file.close()
В данном примере контекстом будет вызов метода write.
13 мая 2005 года вышел PEP-343, который определил новое ключевое слово with, а также методы __enter__() и __exit__(), которые выполняются соответственно при входе и выходе из блока with.
# Запись в файл c использованием менеджера контекста with open("demo_1.txt", "w") as file: file.write("1!") with open("demo_2.txt", "w") as file: file.write("2!")
Количество дуплицированного кода уменьшилось, читабельность улучшилась, функционал остался прежним.
Менеджер контекста
Менеджером контекста называется объект, реализующий вышеобозначенные методы __enter__() и __exit__().
В примере выше функция open может выглядеть как менеджер контекста, однако это не совсем так — им является результат ее выполнения, а именно файловый дескриптор.
Реализация
Простейшая реализация
Рассмотрим пример использования менеджера контекста для отсчета времени выполнения некоторого блока кода. Пример взят из реального проекта, хоть и несколько упрощен для наглядности.
# Без использования менеджера контекста start = time.clock() … foo(...) … executionl_time = time.clock() - start print(float(executionl_time)) start = time.clock() … bar(...) … executionl_time = time.clock() - start print(float(executionl_time))
Создадим менеджер контекста timer
# Менеджер контекста часто определяется в отдельном файле. # В этом конкретном случае это был файл Utils.py class timer(object): def __enter__(self): self.t = time.clock() return self def __exit__(self, type, value, traceback): self.e = time.clock() def __float__(self): return float(self.e - self.t) # Код в другом файле with timer() as t1: … foo(...) … print(t1) with timer() as t2: … bar(...) … print(t2)
Стоит отметить, что метод __exit__() всегда принимает 4 аргумента в следующем порядке:
-
self— ссылка на объект, служит для обращения к собственным переменным и методам. -
exception_type— тип исключения. -
exception_value— объект исключения. -
traceback— объект, содержащий информацию о последовательности вызовов, которые предшествовали исключению.
Эти данные можно использовать для более тонкой обработки исключений или для отладки. Имена аргументов остаются на усмотрение разработчика.
Реализация с использованием contextmanager
В PEP 343 также был представлен и более элегантный и эффективный способ создания менеджера контекста, а именно использование декортора contextmanager.
Генератор созданный с помощью декоратора contextmanager возвращает объект с автоматически созданными необходимыми методами __enter__() и __exit__(). В таком случае метод __enter__() возвращает next(), выполняя код до ключевого слова yield. __exit__() же, пытаясь получить второй next(), выполняет код после.
from contextlib import contextmanager @contextmanager def context_manager(): # Внутри вызова __enter__ print("Enter") try: yield finally: # Внутри вызова __exit__ print("Exit") with context_manager(): print("Hello") # >> Enter # >> Hello # >> Exit
Именно последний вариант стал общепринятым стандартом разработки.
Использование
Возможности использования менеджера контекста в разработке куда шире, чем многие привыкли думать. Наиболее часто упоминается пример с открытием файла для чтения или записи, а также создание соединения при работе с базами данных. Однако, я бы хотел перечислить несколько других мест, где использование менеджера контекста не только оправдано, но и приносит значительный вклад в упрощение как разработки кода, так и его поддержки в дальнейшем.
Следующие примеры взяты из нескольких наиболее популярных open-source проектов на Python. Каждый пример содержит определение менеджера контекста и его использование.
Scrapy
https://github.com/scrapy/scrapy
В репозитории scrapy, созданном для сканирования и парсинга веб страниц, можно найти следующий пример использования менеджера контекста. Здесь он использован с целью выставления временных переменных среды, возвратом к предыдущему состоянию после выхода из контеста, выполнения блока кода под оператором with.
# scrapy\scrapy\utils\misc.py @contextmanager def set_environ(**kwargs): """Temporarily set environment variables inside the context … """ original_env = {k: os.environ.get(k) for k in kwargs} os.environ.update(kwargs) try: yield finally: for k, v in original_env.items(): if v is None: del os.environ[k] else: os.environ[k] = v # Где-то в другом файле … with set_environ(SCRAPY_CHECK='true'): for spidername in args or spider_loader.list(): spidercls = spider_loader.load(spidername) …
Rich
https://github.com/Textualize/rich
Менеджер контекста может быть использован и просто для улучшения читаемости кода. Предлагаю обратить внимание на имплементацию демо функционала в известном репозитории rich, созданном для того, чтобы предоставить функционал для работы с текстом. Каждое действие здесь, в коде демо презентации, сопровождается небольшой паузой по окончанию выполнения для улучшения восприятия зрителем. Вместо того, чтобы вызывать time.sleep каждый раз, разработчик нашел элегантное решение с менеджером контекста beat.
# rich\examples\table_movie.py BEAT_TIME = 0.04 @contextmanager def beat(length: int = 1) -> None: yield time.sleep(length * BEAT_TIME) # Где-то в другом файле … with beat(10): table.add_column("Release Date", no_wrap=True) with beat(10): table.add_column("Title", Text.from_markup("[b]Total", justify="right")) …
Cert
https://github.com/certbot/certbot
Данный пример взят из репозитория cert, созданным облегчить работу с сертификатами для https соединения. В нем разработчик использует менеджер контекста для того, чтобы гарантировать возврат к старой маске разрешений пользователя после того, как выполнится блок кода.
# certbot\certbot\certbot\compat\filesystem.py @contextmanager def temp_umask(mask: int) -> Generator[None, None, None]: """ Apply a umask temporarily, meant to be used in a `with` block. Uses the Certbot implementation of umask. :param int mask: The user file-creation mode mask to apply temporarily """ old_umask: Optional[int] = None try: old_umask = umask(mask) yield None finally: if old_umask is not None: umask(old_umask) # Где-то в другом файле … with filesystem.temp_umask(0o022): util.set_up_core_dir(...) …
Заключение
Менеджер контекста — инструмент, который может и должен быть использован для организации кода в случае, когда это работает на улучшение читаемости или уменьшение дупликации кода. Хороший признак того, что стоит задуматься над созданием менеджера контекста, это обнаружение повторяющегося блока кода с явно выраженными подготовкой перед и завершением после выполнения основной функциональности.
ссылка на оригинал статьи https://habr.com/ru/articles/724030/
Добавить комментарий