# `contextlib` — Утиліти для контекстів операторів `with`

Вихідний код: Lib/contextlib.py

Цей модуль надає утиліти для типових завдань, пов’язаних із оператором `with`. Для отримання додаткової інформації див. також Типи менеджера контексту і З менеджерами контексту операторів.

## Комунальні послуги

Надані функції та класи:

class contextlib.AbstractContextManager

  abstract base class для класів, які реалізують `object.__enter__()` і `object.__exit__()`. Надається реалізація за замовчуванням для `object.__enter__()`, яка повертає `self`, тоді як `object.__exit__()` є абстрактним методом, який за замовчуванням повертає `None`. Дивіться також визначення Типи менеджера контексту.

  Added in version 3.6.

class contextlib.AbstractAsyncContextManager

  abstract base class для класів, які реалізують `object.__aenter__()` і `object.__aexit__()`. Надається реалізація за замовчуванням для `object.__aenter__()`, яка повертає `self`, тоді як `object.__aexit__()` є абстрактним методом, який за замовчуванням повертає `None`. Дивіться також визначення Менеджери асинхронного контексту.

  Added in version 3.7.

@contextlib.contextmanager

  This function is a decorator that can be used to define a factory function for `with` statement context managers, without needing to create a class or separate `__enter__()` and `__exit__()` methods.

  Хоча багато об’єктів нативно підтримують використання операторів in with, інколи необхідно керувати ресурсом, який сам по собі не є менеджером контексту та не реалізує метод `close()` для використання з `contextlib. закриття`

  Абстрактним прикладом може бути наступне, щоб забезпечити правильне керування ресурсами:

      from contextlib import contextmanager

      @contextmanager
      def managed_resource(*args, **kwds):
          # Code to acquire resource, e.g.:
          resource = acquire_resource(*args, **kwds)
          try:
              yield resource
          finally:
              # Code to release resource, e.g.:
              release_resource(resource)

  The function can then be used like this:

      >>> with managed_resource(timeout=3600) as resource:
      ...     # Resource is released at the end of this block,
      ...     # even if code in the block raises an exception

  Функція, яка декорується, має повертати generator-ітератор під час виклику. Цей ітератор має давати рівно одне значення, яке буде прив’язано до цілей у пропозиції `as` інструкції `with`, якщо така є.

  У точці, де генератор поступається, виконується блок, вкладений у оператор `with`. Потім генератор відновлюється після виходу з блоку. Якщо в блоці виникає необроблена виняткова ситуація, вона повторно створюється всередині генератора в точці, де відбувся вихід. Таким чином, ви можете використовувати оператор `try`…`except`…`finally`, щоб перехопити помилку (якщо така є) або забезпечити виконання певного очищення. Якщо виняток перехоплюється лише для того, щоб зареєструвати його або виконати певну дію (а не повністю його придушити), генератор повинен повторно викликати цей виняток. В іншому випадку менеджер контексту генератора вкаже оператору `with`, що виняток було оброблено, і виконання буде відновлено оператором, що слідує безпосередньо за оператором `with`.

  `contextmanager()` використовує `ContextDecorator`, тому створені ним менеджери контексту можна використовувати як декоратори, а також у операторах `with`. Коли використовується як декоратор, новий екземпляр генератора неявно створюється під час кожного виклику функції (це дозволяє «одноразовим» контекстним менеджерам, створеним `contextmanager()`, відповідати вимогам, щоб контекстні менеджери підтримували кілька викликів, щоб використовувати як декоратори).

  Змінено в версії 3.2: Використання `ContextDecorator`.

@contextlib.asynccontextmanager

  > Подібно до `contextmanager()`, але створює асинхронний менеджер контексту.

  > This function is a decorator that can be used to define a factory function for `async with` statement asynchronous context managers, without needing to create a class or separate `__aenter__()` and `__aexit__()` methods. It must be applied to an asynchronous generator function.

  > Простий приклад:

  >     from contextlib import asynccontextmanager

  >     @asynccontextmanager
  >     async def get_connection():
  >         conn = await acquire_db_connection()
  >         try:
  >             yield conn
  >         finally:
  >             await release_db_connection(conn)

  >     async def get_all_users():
  >         async with get_connection() as conn:
  >             return conn.query('SELECT ...')

  > Added in version 3.7.

  > Менеджери контексту, визначені за допомогою `asynccontextmanager()`, можна використовувати як декоратори або за допомогою операторів `async with`:

  >     import time
  >     from contextlib import asynccontextmanager

  >     @asynccontextmanager
  >     async def timeit():
  >         now = time.monotonic()
  >         try:
  >             yield
  >         finally:
  >             print(f'it took {time.monotonic() - now}s to run')

  >     @timeit()
  >     async def main():
  >         # ... async code ...

  > При використанні як декоратора новий екземпляр генератора неявно створюється під час кожного виклику функції. Це дозволяє «одноразовим» контекстним менеджерам, створеним `asynccontextmanager()`, відповідати вимогам, щоб контекстні менеджери підтримували кілька викликів, щоб використовувати їх як декоратори.

  Змінено в версії 3.10: Менеджери асинхронного контексту, створені за допомогою `asynccontextmanager()`, можна використовувати як декоратори.

contextlib.closing(thing)

  Повертає контекстний менеджер, який закриває річ після завершення блоку. Це в основному еквівалентно:

      from contextlib import contextmanager

      @contextmanager
      def closing(thing):
          try:
              yield thing
          finally:
              thing.close()

  І дозволяє писати такий код:

      from contextlib import closing
      from urllib.request import urlopen

      with closing(urlopen('https://www.python.org')) as page:
          for line in page:
              print(line)

  без необхідності явного закриття `сторінки`. Навіть якщо станеться помилка, `page.close()` буде викликано під час виходу з блоку `with`.

  Примітка

  Most types managing resources support the context manager protocol, which closes thing on leaving the `with` statement. As such, `closing()` is most useful for third party types that don’t support context managers. This example is purely for illustration purposes, as `urlopen()` would normally be used in a context manager.

contextlib.aclosing(thing)

  Повертає менеджер асинхронного контексту, який викликає метод `aclose()` thing після завершення блоку. Це в основному еквівалентно:

      from contextlib import asynccontextmanager

      @asynccontextmanager
      async def aclosing(thing):
          try:
              yield thing
          finally:
              await thing.aclose()

  Важливо, що `aclosing()` підтримує детерміноване очищення асинхронних генераторів, коли вони виходять раніше через `break` або виняток. Наприклад:

      from contextlib import aclosing

      async with aclosing(my_generator()) as values:
          async for value in values:
              if value == 42:
                  break

  Цей шаблон гарантує, що асинхронний код виходу генератора виконується в тому самому контексті, що й його ітерації (щоб винятки та контекстні змінні працювали належним чином, а код виходу не запускався після закінчення часу існування певного завдання, від якого він залежить).

  Added in version 3.10.

contextlib.nullcontext(enter_result=None)

  Повертає контекстний менеджер, який повертає enter_result із `__enter__`, але в іншому випадку нічого не робить. Він призначений для використання як резервний для додаткового контекстного менеджера, наприклад:

      def myfunction(arg, ignore_exceptions=False):
          if ignore_exceptions:
              # Use suppress to ignore all exceptions.
              cm = contextlib.suppress(Exception)
          else:
              # Do not ignore any exceptions, cm has no effect.
              cm = contextlib.nullcontext()
          with cm:
              # Do something

  Приклад використання enter_result:

      def process_file(file_or_path):
          if isinstance(file_or_path, str):
              # If string, open file
              cm = open(file_or_path)
          else:
              # Caller is responsible for closing file
              cm = nullcontext(file_or_path)

          with cm as file:
              # Perform processing on the file

  Його також можна використовувати як заміну для асинхронних контекстних менеджерів:

      async def send_http(session=None):
          if not session:
              # If no http session, create it with aiohttp
              cm = aiohttp.ClientSession()
          else:
              # Caller is responsible for closing the session
              cm = nullcontext(session)

          async with cm as session:
              # Send http requests with session

  Added in version 3.7.

  Змінено в версії 3.10: Додано підтримку asynchronous context manager.

contextlib.suppress(*exceptions)

  Повертає диспетчер контексту, який пригнічує будь-які з указаних винятків, якщо вони трапляються в тілі оператора `with`, а потім відновлює виконання з першим оператором, що йде після кінця оператора `with`.

  Як і будь-який інший механізм, який повністю пригнічує винятки, цей менеджер контексту слід використовувати лише для покриття дуже конкретних помилок, коли, як відомо, правильно продовжувати виконання програми.

  Наприклад:

      from contextlib import suppress

      with suppress(FileNotFoundError):
          os.remove('somefile.tmp')

      with suppress(FileNotFoundError):
          os.remove('someotherfile.tmp')

  Цей код еквівалентний:

      try:
          os.remove('somefile.tmp')
      except FileNotFoundError:
          pass

      try:
          os.remove('someotherfile.tmp')
      except FileNotFoundError:
          pass

  Цей контекстний менеджер є reentrant.

  If the code within the `with` block raises a `BaseExceptionGroup`, suppressed exceptions are removed from the group. Any exceptions of the group which are not suppressed are re-raised in a new group which is created using the original group’s `derive()` method.

  Added in version 3.4.

  Змінено в версії 3.12: `suppress` now supports suppressing exceptions raised as part of a `BaseExceptionGroup`.

contextlib.redirect_stdout(new_target)

  Менеджер контексту для тимчасового переспрямування `sys.stdout` на інший файл або файлоподібний об’єкт.

  Цей інструмент додає гнучкості існуючим функціям або класам, вихід яких підключено до стандартного виводу.

  Наприклад, вихід `help()` зазвичай надсилається до sys.stdout. Ви можете записати цей вивід у рядок, перенаправивши вивід на об’єкт `io.StringIO`. Потік заміни повертається з методу `__enter__` і тому доступний як ціль оператора `with`:

      with redirect_stdout(io.StringIO()) as f:
          help(pow)
      s = f.getvalue()

  Щоб надіслати вивід `help()` у файл на диску, перенаправте вивід у звичайний файл:

      with open('help.txt', 'w') as f:
          with redirect_stdout(f):
              help(pow)

  Щоб надіслати результат `help()` до sys.stderr:

      with redirect_stdout(sys.stderr):
          help(pow)

  Зауважте, що глобальний побічний ефект `sys.stdout` означає, що цей контекстний менеджер не підходить для використання в бібліотечному коді та більшості потокових програм. Це також не впливає на вихідні дані підпроцесів. Однак це все ще корисний підхід для багатьох службових сценаріїв.

  Цей контекстний менеджер є reentrant.

  Added in version 3.4.

contextlib.redirect_stderr(new_target)

  Подібно до `redirect_stdout()`, але перенаправляє `sys.stderr` до іншого файлу або файлоподібного об’єкта.

  Цей контекстний менеджер є reentrant.

  Added in version 3.5.

contextlib.chdir(path)

  Non parallel-safe context manager to change the current working directory. As this changes a global state, the working directory, it is not suitable for use in most threaded or async contexts. It is also not suitable for most non-linear code execution, like generators, where the program execution is temporarily relinquished – unless explicitly desired, you should not yield when this context manager is active.

  This is a simple wrapper around `chdir()`, it changes the current working directory upon entering and restores the old one on exit.

  Цей контекстний менеджер є reentrant.

  Added in version 3.11.

class contextlib.ContextDecorator

  Базовий клас, який дозволяє менеджеру контексту також використовуватися як декоратор.

  Менеджери контексту, успадковані від `ContextDecorator`, мають реалізувати `__enter__` і `__exit__` як зазвичай. `__exit__` зберігає необов’язкову обробку винятків, навіть якщо використовується як декоратор.

  `ContextDecorator` використовується `contextmanager()`, тому ви отримуєте цю функціональність автоматично.

  Приклад `ContextDecorator`:

      from contextlib import ContextDecorator

      class mycontext(ContextDecorator):
          def __enter__(self):
              print('Starting')
              return self

          def __exit__(self, *exc):
              print('Finishing')
              return False

  The class can then be used like this:

      >>> @mycontext()
      ... def function():
      ...     print('The bit in the middle')
      ...
      >>> function()
      Starting
      The bit in the middle
      Finishing

      >>> with mycontext():
      ...     print('The bit in the middle')
      ...
      Starting
      The bit in the middle
      Finishing

  Ця зміна є просто синтаксичним цукром для будь-якої конструкції наступної форми:

      def f():
          with cm():
              # Do stuff

  `ContextDecorator` дозволяє замість цього писати:

      @cm()
      def f():
          # Do stuff

  Це дає зрозуміти, що `cm` застосовується до всієї функції, а не лише до її частини (і зберегти рівень відступу теж добре).

  Існуючі контекстні менеджери, які вже мають базовий клас, можна розширити за допомогою `ContextDecorator` як класу mixin:

      from contextlib import ContextDecorator

      class mycontext(ContextBaseClass, ContextDecorator):
          def __enter__(self):
              return self

          def __exit__(self, *exc):
              return False

  Примітка

  Оскільки декорована функція повинна мати можливість викликатися кілька разів, базовий менеджер контексту повинен підтримувати використання в кількох операторах `with`. Якщо це не так, тоді слід використовувати оригінальну конструкцію з явним оператором `with` усередині функції.

  Added in version 3.2.

class contextlib.AsyncContextDecorator

  Подібно до `ContextDecorator`, але лише для асинхронних функцій.

  Приклад `AsyncContextDecorator`:

      from asyncio import run
      from contextlib import AsyncContextDecorator

      class mycontext(AsyncContextDecorator):
          async def __aenter__(self):
              print('Starting')
              return self

          async def __aexit__(self, *exc):
              print('Finishing')
              return False

  The class can then be used like this:

      >>> @mycontext()
      ... async def function():
      ...     print('The bit in the middle')
      ...
      >>> run(function())
      Starting
      The bit in the middle
      Finishing

      >>> async def function():
      ...    async with mycontext():
      ...         print('The bit in the middle')
      ...
      >>> run(function())
      Starting
      The bit in the middle
      Finishing

  Added in version 3.10.

class contextlib.ExitStack

  Менеджер контексту, розроблений для полегшення програмного поєднання інших менеджерів контексту та функцій очищення, особливо тих, які є необов’язковими або іншим чином керуються вхідними даними.

  Наприклад, набір файлів можна легко обробити в одному операторі with наступним чином:

      with ExitStack() as stack:
          files = [stack.enter_context(open(fname)) for fname in filenames]
          # All opened files will automatically be closed at the end of
          # the with statement, even if attempts to open files later
          # in the list raise an exception

  The `__enter__()` method returns the `ExitStack` instance, and performs no additional operations.

  Кожен екземпляр підтримує стек зареєстрованих зворотних викликів, які викликаються у зворотному порядку, коли екземпляр закривається (явно чи неявно в кінці оператора `with`). Зауважте, що зворотні виклики не викликаються неявно, коли примірник стеку контексту збирається сміттям.

  Ця модель стеку використовується для того, щоб контекстні менеджери, які отримують свої ресурси у своєму методі `__init__` (наприклад, файлові об’єкти), могли оброблятися правильно.

  Оскільки зареєстровані зворотні виклики викликаються в порядку, зворотному реєстрації, це в кінцевому підсумку поводиться так, ніби декілька вкладених операторів `with` використовувалися із зареєстрованим набором зворотних викликів. Це навіть поширюється на обробку винятків - якщо внутрішній зворотний виклик пригнічує або замінює виняток, то зовнішнім зворотним викликам будуть передані аргументи на основі цього оновленого стану.

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

  Added in version 3.3.

  enter_context(cm)

    Enters a new context manager and adds its `__exit__()` method to the callback stack. The return value is the result of the context manager’s own `__enter__()` method.

    Ці контекстні менеджери можуть придушувати винятки так само, як вони зазвичай робили б, якщо використовувати безпосередньо як частину оператора `with`.

    Змінено в версії 3.11: Raises `TypeError` instead of `AttributeError` if cm is not a context manager.

  push(exit)

    Adds a context manager’s `__exit__()` method to the callback stack.

    As `__enter__` is not invoked, this method can be used to cover part of an `__enter__()` implementation with a context manager’s own `__exit__()` method.

    If passed an object that is not a context manager, this method assumes it is a callback with the same signature as a context manager’s `__exit__()` method and adds it directly to the callback stack.

    By returning true values, these callbacks can suppress exceptions the same way context manager `__exit__()` methods can.

    Переданий об’єкт повертається функцією, що дозволяє використовувати цей метод як декоратор функції.

  callback(callback, /, *args, **kwds)

    Приймає довільну функцію зворотного виклику та аргументи та додає їх до стеку зворотного виклику.

    На відміну від інших методів, зворотні виклики, додані таким чином, не можуть придушувати винятки (оскільки їм ніколи не передаються деталі винятку).

    Переданий зворотний виклик повертається функцією, що дозволяє використовувати цей метод як декоратор функції.

  pop_all()

    Передає стек зворотних викликів до нового екземпляра `ExitStack` і повертає його. Жодні зворотні виклики не викликаються цією операцією - замість цього вони тепер будуть викликані, коли новий стек закривається (явно чи неявно в кінці оператора `with`).

    Наприклад, групу файлів можна відкрити за допомогою операції «все або нічого» наступним чином:

        with ExitStack() as stack:
            files = [stack.enter_context(open(fname)) for fname in filenames]
            # Hold onto the close method, but don't call it yet.
            close_files = stack.pop_all().close
            # If opening any file fails, all previously opened files will be
            # closed automatically. If all files are opened successfully,
            # they will remain open even after the with statement ends.
            # close_files() can then be invoked explicitly to close them all.

  close()

    Негайно розгортає стек зворотних викликів, викликаючи зворотні виклики в порядку, зворотному реєстрації. Для будь-яких менеджерів контексту та зареєстрованих зворотних викликів виходу передані аргументи вказуватимуть, що винятків не сталося.

class contextlib.AsyncExitStack

  асинхронний контекстний менеджер, подібний до `ExitStack`, який підтримує поєднання синхронних і асинхронних контекстних менеджерів, а також має співпрограми для логіки очищення.

  The `close()` method is not implemented; `aclose()` must be used instead.

  coroutine enter_async_context(cm)

    Similar to `ExitStack.enter_context()` but expects an asynchronous context manager.

    Змінено в версії 3.11: Raises `TypeError` instead of `AttributeError` if cm is not an asynchronous context manager.

  push_async_exit(exit)

    Similar to `ExitStack.push()` but expects either an asynchronous context manager or a coroutine function.

  push_async_callback(callback, /, *args, **kwds)

    Similar to `ExitStack.callback()` but expects a coroutine function.

  coroutine aclose()

    Similar to `ExitStack.close()` but properly handles awaitables.

  Продовжуємо приклад для `asynccontextmanager()`:

      async with AsyncExitStack() as stack:
          connections = [await stack.enter_async_context(get_connection())
              for i in range(5)]
          # All opened connections will automatically be released at the end of
          # the async with statement, even if attempts to open a connection
          # later in the list raise an exception.

  Added in version 3.7.

## Приклади та рецепти

У цьому розділі описано деякі приклади та рецепти ефективного використання інструментів, наданих `contextlib`.

### Підтримка змінної кількості контекстних менеджерів

Основний варіант використання `ExitStack` — це той, який наведено в документації класу: підтримка змінної кількості контекстних менеджерів та інших операцій очищення в одному операторі `with`. Варіативність може виникати через кількість необхідних менеджерів контексту, які керуються введенням користувача (наприклад, відкриття вказаної користувачем колекції файлів), або через те, що деякі менеджери контексту є необов’язковими:

    with ExitStack() as stack:
        for resource in resources:
            stack.enter_context(resource)
        if need_special_resource():
            special = acquire_special_resource()
            stack.callback(release_special_resource, special)
        # Perform operations that use the acquired resources

Як показано, `ExitStack` також дозволяє досить легко використовувати оператори `with` для керування довільними ресурсами, які спочатку не підтримують протокол керування контекстом.

### Перехоплення винятків із методів `__enter__`

Час від часу бажано перехоплювати винятки з реалізації методу `__enter__`, без випадкового перехоплення винятків з тіла оператора `with` або методу `__exit__` контекстного менеджера. За допомогою `ExitStack` кроки в протоколі керування контекстом можна трохи розділити, щоб дозволити це:

    stack = ExitStack()
    try:
        x = stack.enter_context(cm)
    except Exception:
        # handle __enter__ exception
    else:
        with stack:
            # Handle normal case

Насправді необхідність зробити це означає, що основний API має надавати інтерфейс прямого керування ресурсами для використання з операторами `try`/`except`/`finally`, але не з усіма API добре розроблені в цьому плані. Коли контекстний менеджер є єдиним наданим API керування ресурсами, тоді `ExitStack` може полегшити обробку різноманітних ситуацій, які не можна обробити безпосередньо в операторі `with`.

### Очищення в реалізації `__enter__`

As noted in the documentation of `ExitStack.push()`, this method can be useful in cleaning up an already allocated resource if later steps in the `__enter__()` implementation fail.

Ось приклад виконання цього для контекстного менеджера, який приймає функції отримання та звільнення ресурсів разом із додатковою функцією перевірки та відображає їх у протоколі керування контекстом:

    from contextlib import contextmanager, AbstractContextManager, ExitStack

    class ResourceManager(AbstractContextManager):

        def __init__(self, acquire_resource, release_resource, check_resource_ok=None):
            self.acquire_resource = acquire_resource
            self.release_resource = release_resource
            if check_resource_ok is None:
                def check_resource_ok(resource):
                    return True
            self.check_resource_ok = check_resource_ok

        @contextmanager
        def _cleanup_on_error(self):
            with ExitStack() as stack:
                stack.push(self)
                yield
                # The validation check passed and didn't raise an exception
                # Accordingly, we want to keep the resource, and pass it
                # back to our caller
                stack.pop_all()

        def __enter__(self):
            resource = self.acquire_resource()
            with self._cleanup_on_error():
                if not self.check_resource_ok(resource):
                    msg = "Failed validation for {!r}"
                    raise RuntimeError(msg.format(resource))
            return resource

        def __exit__(self, *exc_details):
            # We don't need to duplicate any of our resource release logic
            self.release_resource()

### Заміна будь-якого використання змінних `try-finally` і прапорців

Зразок, який ви іноді побачите, — це інструкція `try-finally` зі змінною-прапором, яка вказує, чи має бути виконано тіло пропозиції `finally`. У своїй найпростішій формі (з якою вже не можна впоратися лише за допомогою пропозиції `except`), це виглядає приблизно так:

    cleanup_needed = True
    try:
        result = perform_operation()
        if result:
            cleanup_needed = False
    finally:
        if cleanup_needed:
            cleanup_resources()

Як і у випадку з будь-яким кодом, заснованим на інструкціях `try`, це може спричинити проблеми з розробкою та переглядом, оскільки код налаштування та код очищення можуть бути розділені довільно довгими частинами коду.

`ExitStack` дає змогу замість цього зареєструвати зворотний виклик для виконання в кінці оператора `with`, а потім вирішити пропустити виконання цього зворотного виклику:

    from contextlib import ExitStack

    with ExitStack() as stack:
        stack.callback(cleanup_resources)
        result = perform_operation()
        if result:
            stack.pop_all()

This allows the intended cleanup behaviour to be made explicit up front, rather than requiring a separate flag variable.

Якщо певна програма часто використовує цей шаблон, його можна ще більше спростити за допомогою невеликого допоміжного класу:

    from contextlib import ExitStack

    class Callback(ExitStack):
        def __init__(self, callback, /, *args, **kwds):
            super().__init__()
            self.callback(callback, *args, **kwds)

        def cancel(self):
            self.pop_all()

    with Callback(cleanup_resources) as cb:
        result = perform_operation()
        if result:
            cb.cancel()

Якщо очищення ресурсу ще не акуратно об’єднано в окрему функцію, то все ще можна використовувати форму декоратора `ExitStack.callback()`, щоб оголосити очищення ресурсу заздалегідь:

    from contextlib import ExitStack

    with ExitStack() as stack:
        @stack.callback
        def cleanup_resources():
            ...
        result = perform_operation()
        if result:
            stack.pop_all()

Через те, як працює протокол декоратора, функція зворотного виклику, оголошена таким чином, не може приймати жодних параметрів. Натомість доступ до будь-яких ресурсів, які потрібно звільнити, має здійснюватися як змінні закриття.

### Використання контекстного менеджера як декоратора функції

`ContextDecorator` дає змогу використовувати менеджер контексту як у звичайному операторі `with`, так і як декоратор функції.

Наприклад, іноді корисно обернути функції або групи операторів за допомогою реєстратора, який може відстежувати час входу та час виходу. Замість написання як декоратора функції, так і менеджера контексту для завдання, успадкування від `ContextDecorator` надає обидві можливості в одному визначенні:

    from contextlib import ContextDecorator
    import logging

    logging.basicConfig(level=logging.INFO)

    class track_entry_and_exit(ContextDecorator):
        def __init__(self, name):
            self.name = name

        def __enter__(self):
            logging.info('Entering: %s', self.name)

        def __exit__(self, exc_type, exc, exc_tb):
            logging.info('Exiting: %s', self.name)

Екземпляри цього класу можна використовувати і як менеджер контексту:

    with track_entry_and_exit('widget loader'):
        print('Some time consuming activity goes here')
        load_widget()

А також як декоратор функцій:

    @track_entry_and_exit('widget loader')
    def activity():
        print('Some time consuming activity goes here')
        load_widget()

Note that there is one additional limitation when using context managers as function decorators: there’s no way to access the return value of `__enter__()`. If that value is needed, then it is still necessary to use an explicit `with` statement.

Дивись також

PEP 343 - оператор «з».

  Специфікація, передумови та приклади оператора Python `with`.

## Менеджери контексту для одноразового, багаторазового та повторного входу

Більшість контекстних менеджерів написані таким чином, що вони можуть бути ефективно використані в операторі `with` лише один раз. Ці одноразові контекстні менеджери потрібно створювати заново кожного разу, коли вони використовуються — спроба використати їх вдруге призведе до виключення або іншим чином не працюватиме належним чином.

Це загальне обмеження означає, що загалом доцільно створювати менеджери контексту безпосередньо в заголовку оператора `with`, де вони використовуються (як показано в усіх наведених вище прикладах використання).

Файли є прикладом ефективних одноразових контекстних менеджерів, оскільки перший оператор `with` закриває файл, запобігаючи будь-яким подальшим операціям вводу-виводу з використанням цього файлового об’єкта.

Менеджери контексту, створені за допомогою `contextmanager()`, також є одноразовими менеджерами контексту, і вони скаржаться на те, що базовий генератор не працює, якщо буде зроблена спроба використати їх вдруге:

    >>> from contextlib import contextmanager
    >>> @contextmanager
    ... def singleuse():
    ...     print("Before")
    ...     yield
    ...     print("After")
    ...
    >>> cm = singleuse()
    >>> with cm:
    ...     pass
    ...
    Before
    After
    >>> with cm:
    ...     pass
    ...
    Traceback (most recent call last):
        ...
    RuntimeError: generator didn't yield

### Реентерабельні контекстні менеджери

Більш складні контекстні менеджери можуть бути «реентерабельними». Ці менеджери контексту можна використовувати не лише в кількох операторах `with`, але також всередині оператора `with`, який уже використовує той самий менеджер контексту.

`threading.RLock` is an example of a reentrant context manager, as are `suppress()`, `redirect_stdout()`, and `chdir()`. Here’s a very simple example of reentrant use:

    >>> from contextlib import redirect_stdout
    >>> from io import StringIO
    >>> stream = StringIO()
    >>> write_to_stream = redirect_stdout(stream)
    >>> with write_to_stream:
    ...     print("This is written to the stream rather than stdout")
    ...     with write_to_stream:
    ...         print("This is also written to the stream")
    ...
    >>> print("This is written directly to stdout")
    This is written directly to stdout
    >>> print(stream.getvalue())
    This is written to the stream rather than stdout
    This is also written to the stream

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

Зауважте також, що реентерабельність — це не те саме, що бути потокобезпечною. `redirect_stdout()`, наприклад, точно небезпечний для потоків, оскільки він робить глобальну модифікацію стану системи шляхом прив’язки `sys.stdout` до іншого потоку.

### Багаторазові контекстні менеджери

Від одноразових і повторних контекстних менеджерів відрізняються «повторно використовувані» контекстні менеджери (або, якщо бути повністю явними, «повторно використовувані, але не повторні» контекстні менеджери, оскільки повторні контекстні менеджери також багаторазові). Ці контекстні менеджери підтримують багаторазове використання, але не працюватимуть (або не працюватимуть належним чином), якщо конкретний екземпляр контекстного менеджера вже використовувався в операторі containing with.

`threading.Lock` є прикладом повторно використовуваного, але не реентрантного, контекстного менеджера (для повторного вхідного блокування замість цього необхідно використовувати `threading.RLock`).

Іншим прикладом повторно використовуваного, але не реентрантного контекстного менеджера є `ExitStack`, оскільки він викликає всі зареєстровані наразі зворотні виклики, коли залишає будь-який оператор with, незалежно від того, де ці зворотні виклики було додано:

    >>> from contextlib import ExitStack
    >>> stack = ExitStack()
    >>> with stack:
    ...     stack.callback(print, "Callback: from first context")
    ...     print("Leaving first context")
    ...
    Leaving first context
    Callback: from first context
    >>> with stack:
    ...     stack.callback(print, "Callback: from second context")
    ...     print("Leaving second context")
    ...
    Leaving second context
    Callback: from second context
    >>> with stack:
    ...     stack.callback(print, "Callback: from outer context")
    ...     with stack:
    ...         stack.callback(print, "Callback: from inner context")
    ...         print("Leaving inner context")
    ...     print("Leaving outer context")
    ...
    Leaving inner context
    Callback: from inner context
    Callback: from outer context
    Leaving outer context

Як показує вихід із прикладу, повторне використання одного об’єкта стека в кількох інструкціях with працює правильно, але спроба їх вкладення спричинить очищення стека в кінці внутрішнього оператора with, що навряд чи буде бажаною поведінкою.

Використання окремих екземплярів `ExitStack` замість повторного використання одного екземпляра дозволяє уникнути цієї проблеми:

    >>> from contextlib import ExitStack
    >>> with ExitStack() as outer_stack:
    ...     outer_stack.callback(print, "Callback: from outer context")
    ...     with ExitStack() as inner_stack:
    ...         inner_stack.callback(print, "Callback: from inner context")
    ...         print("Leaving inner context")
    ...     print("Leaving outer context")
    ...
    Leaving inner context
    Callback: from inner context
    Leaving outer context
    Callback: from outer context