На этом шаге мы рассмотрим способ решения этой задачи.
Вы хотите реализовать новые менеджеры контекста для использования с инструкцией with.
Один из самых простых способов написания нового менеджера контекста - использовать декоратор @contextmanager из модуля contextlib. Вот пример менеджера контекста, подсчитывающего время выполнения блока кода:
>>> import time >>> from contextlib import contextmanager >>> @contextmanager def timethis(label): start = time.time() try: yield finally: end = time.time() print('{}: {}'.format(label, end - start)) >>> # Пример >>> with timethis('counting'): n = 10000000 while n > 0: n -= 1 counting: 1.9680047035217285 >>>
В функции timethis() весь код перед yield выполняется как метод __enter__() менеджера контекста. Весь код после yield выполняется как метод __exit__(). Если имеет место исключение, оно возбуждается в инструкции yield.
Вот немного более продвинутый пример менеджера контекста, который реализует некую транзакцию на объекте списка:
>>> from contextlib import contextmanager >>> @contextmanager def list_transaction(orig_list): working = list(orig_list) yield working orig_list[:] = working
Идея в том, что изменения вносятся в список только в том случае, если при выполнении всего блока кода не возбуждаются исключения. Вот пример:
>>> items = [1, 2, 3] >>> with list_transaction(items) as working: working.append(4) working.append(5) >>> items [1, 2, 3, 4, 5] >>> with list_transaction(items) as working: working.append(6) working.append(7) raise RuntimeError('oops') Traceback (most recent call last): File "<pyshell#16>", line 4, in <module> raise RuntimeError('oops') RuntimeError: oops >>> items [1, 2, 3, 4, 5] >>>
Чтобы написать менеджер контекста, обычно вы определяете класс с методами __enter__() и __exit__():
import time class timethis: def __init__(self, label): self.label = label def __enter__(self): self.start = time.time() def __exit__(self, exc_ty, exc_val, exc_tb): end = time.time() print('{}: {}'.format(self.label, end - self.start))
Хотя это несложно, но все же более утомительно, чем писать простые функции с декоратором @contextmanager.
@contextmanager на самом деле используется для написания замкнутых на себя функций с управлением контекстом. Если вам нужно реализовать поддержку инструкции with каким-либо объектом (например, файлом, сетевым соединением или блокировкой), вам все равно придется отдельно реализовать методы __enter__() и __exit__().
На следующем шаге мы рассмотрим выполнение кода с локальными побочными эффектами.