# `contextlib` — `with` 문 컨텍스트를 위한 유틸리티

소스 코드: Lib/contextlib.py

이 모듈은 `with` 문이 수반되는 일반적인 작업을 위한 유틸리티를 제공합니다. 자세한 정보는 컨텍스트 관리자 형 과 with 문 컨텍스트 관리자도 참조하십시오.

## 유틸리티

제공되는 함수와 클래스:

class contextlib.AbstractContextManager

  An abstract base class for classes that implement `__enter__()` and `__exit__()`. A default implementation for `__enter__()` is provided which returns `self` while `__exit__()` is an abstract method which by default returns `None`. See also the definition of 컨텍스트 관리자 형.

  Added in version 3.6.

class contextlib.AbstractAsyncContextManager

  An abstract base class for classes that implement `__aenter__()` and `__aexit__()`. A default implementation for `__aenter__()` is provided which returns `self` while `__aexit__()` is an abstract method which by default returns `None`. See also the definition of 비동기 컨텍스트 관리자.

  Added in version 3.7.

@contextlib.contextmanager

  이 함수는 클래스나 별도의 `__enter__()`와 `__exit__()` 메서드를 작성할 필요 없이, `with` 문 컨텍스트 관리자를 위한 팩토리 함수를 정의하는 데 사용할 수 있는 데코레이터입니다.

  많은 객체가 자체적으로 with 문에서의 사용을 지원하지만, 스스로는 컨텍스트 관리자가 아니고 `contextlib.closing`과 함께 사용할 `close()` 메서드를 구현하지 않는 자원을 관리해야 하는 경우가 있습니다.

  올바른 자원 관리를 보장하기 위한 추상적인 예는 다음과 같습니다:

      from contextlib import contextmanager

      @contextmanager
      def managed_resource(*args, **kwds):
          # 자원을 얻는 코드, 예를 들어:
          resource = acquire_resource(*args, **kwds)
          try:
              yield resource
          finally:
              # 자원을 해제하는 코드, 예를 들어:
              release_resource(resource)

  The function can then be used like this:

      >>> with managed_resource(timeout=3600) as resource:
      ...     # 블록의 코드에서 예외가 발생하더라도,
      ...     # 이 블록의 끝에서 자원이 해제됩니다

  데코레이트 되는 함수는 호출될 때 제너레이터 이터레이터를 반환해야 합니다. 이 이터레이터는 정확히 하나의 값을 산출해야 하며, 이는 `with` 문의 `as` 절에 있는 대상에 연결됩니다 (있다면).

  제너레이터가 산출하는 지점에서, `with` 문에 중첩된 블록이 실행됩니다. 그런 다음 블록이 종료된 후 제너레이터가 재개합니다. 블록에서 처리되지 않은 예외가 발생하면, 제너레이터 내부의 yield가 등장한 지점에서 다시 발생합니다. 따라서, `try`…`except`…`finally` 문을 사용하여 에러(있다면)를 잡거나, 어떤 정리가 수행되도록 할 수 있습니다. 예외를 단지 로그 하기 위해 또는 (예외를 완전히 억제하는 대신) 어떤 작업을 수행하기 위해 예외를 잡았다면 제너레이터는 해당 예외를 다시 발생시켜야 합니다. 그렇지 않으면 제너레이터 컨텍스트 관리자가 `with` 문에 예외가 처리되었음을 표시하고, `with` 문 바로 다음에 오는 문장으로 실행이 재개됩니다.

  `contextmanager()`는 `ContextDecorator`를 사용해서, 만들어진 컨텍스트 관리자를 `with` 문뿐만 아니라 데코레이터로 사용할 수 있습니다. 데코레이터로 사용될 때, 새로운 제너레이터 인스턴스는 각 함수 호출마다 묵시적으로 만들어집니다 (이는 그렇지 않을 경우 `contextmanager()`에 의해 만들어진 “일회성” 컨텍스트 관리자가 데코레이터로 사용하기 위해 컨텍스트 관리자가 여러 번의 호출을 지원해야 한다는 요구 사항을 충족시킬 수 있도록 합니다) .

  버전 3.2에서 변경: `ContextDecorator` 사용.

@contextlib.asynccontextmanager

  `contextmanager()`와 유사하지만, 비동기 컨텍스트 관리자를 만듭니다.

  이 함수는 클래스나 별도의 `__aenter__()`와 `__aexit__()` 메서드를 작성할 필요 없이, `async with` 문 비동기 컨텍스트 관리자를 위한 팩토리 함수를 정의하는 데 사용할 수 있는 데코레이터입니다. 비동기 제너레이터 함수에 적용해야 합니다.

  간단한 예:

      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.

  Context managers defined with `asynccontextmanager()` can be used either as decorators or with `async with` statements:

      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():
          # ... 비동기 코드 ...

  데코레이터로 사용될 때, 새로운 제너레이터 인스턴스는 각 함수 호출마다 묵시적으로 만들어집니다. 이는 그렇지 않을 경우 `contextmanager()`에 의해 만들어진 “일회성” 컨텍스트 관리자가 데코레이터로 사용하기 위해 컨텍스트 관리자가 여러 번의 호출을 지원해야 한다는 요구 사항을 충족시킬 수 있도록 합니다.

  버전 3.10에서 변경: Async context managers created with `asynccontextmanager()` can be used as decorators.

contextlib.closing(thing)

  블록이 완료될 때 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`를 명시적으로 닫을 필요가 없습니다. 에러가 발생하더라도, `with` 블록이 종료될 때 `page.close()`가 호출됩니다.

  참고

  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)

  블록이 완료될 때 thing의 `aclose()` 메서드를 호출하는 비동기 컨텍스트 관리자를 반환합니다. 이것은 기본적으로 다음과 동등합니다:

      from contextlib import asynccontextmanager

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

  Significantly, `aclosing()` supports deterministic cleanup of async generators when they happen to exit early by `break` or an exception. For example:

      from contextlib import aclosing

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

  This pattern ensures that the generator’s async exit code is executed in the same context as its iterations (so that exceptions and context variables work as expected, and the exit code isn’t run after the lifetime of some task it depends on).

  Added in version 3.10.

contextlib.nullcontext(enter_result=None)

  Return a context manager that returns enter_result from `__enter__()`, but otherwise does nothing. It is intended to be used as a stand-in for an optional context manager, for example:

      def myfunction(arg, ignore_exceptions=False):
          if ignore_exceptions:
              # 모든 예외를 무시하기 위해 suppress를 사용합니다.
              cm = contextlib.suppress(Exception)
          else:
              # 아무런 예외도 무시하지 않습니다, cm은 아무 효과도 없습니다.
              cm = contextlib.nullcontext()
          with cm:
              # 뭔가 합니다

  enter_result를 사용하는 예:

      def process_file(file_or_path):
          if isinstance(file_or_path, str):
              # 문자열이면, 파일을 엽니다
              cm = open(file_or_path)
          else:
              # 파일을 닫는 책임은 호출자에게 있습니다
              cm = nullcontext(file_or_path)

          with cm as file:
              # 파일에 대한 처리를 수행합니다

  또한 비동기 컨텍스트 관리자의 대체로 사용할 수 있습니다:

      async def send_http(session=None):
          if not session:
              # http 세션이 없으면, aiohttp 로 만듭니다
              cm = aiohttp.ClientSession()
          else:
              # 세션을 닫는 책임은 호출자에게 있습니다
              cm = nullcontext(session)

          async with cm as session:
              # 세션으로 http 요청을 보냅니다

  Added in version 3.7.

  버전 3.10에서 변경: asynchronous context manager support was added.

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

  이 컨텍스트 관리자는 재진입 가능합니다.

  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)

  Context manager for temporarily redirecting `sys.stdout` to another file object.

  This tool adds flexibility to existing functions or classes whose output is hardwired to `sys.stdout`.

  For example, the output of `help()` normally is sent to sys.stdout. You can capture that output in a string by redirecting the output to an `io.StringIO` object. The replacement stream is returned from the `__enter__()` method and so is available as the target of the `with` statement:

      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`에 대한 전역 부작용은 이 컨텍스트 관리자가 라이브러리 코드와 대부분의 스레드 응용 프로그램에 사용하기에 적합하지 않음을 의미합니다. 또한 서브 프로세스의 출력에는 영향을 미치지 않습니다. 그러나, 여전히 많은 유틸리티 스크립트에 유용한 접근법입니다.

  이 컨텍스트 관리자는 재진입 가능합니다.

  Added in version 3.4.

contextlib.redirect_stderr(new_target)

  Similar to `redirect_stdout()` but redirecting the global `sys.stderr` to another file object.

  이 컨텍스트 관리자는 재진입 가능합니다.

  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.

  이 컨텍스트 관리자는 재진입 가능합니다.

  Added in version 3.11.

class contextlib.ContextDecorator

  컨텍스트 관리자를 데코레이터로도 사용할 수 있도록 하는 베이스 클래스.

  Context managers inheriting from `ContextDecorator` have to implement `__enter__()` and `__exit__()` as normal. `__exit__` retains its optional exception handling even when used as a decorator.

  `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():
              # 뭔가 합니다

  `ContextDecorator`는 대신 다음과 같이 쓸 수 있도록 합니다:

      @cm()
      def f():
          # 뭔가 합니다

  `cm`이 단지 함수의 일부가 아니라 전체 함수에 적용된다는 것을 분명히 합니다 (그리고 들여쓰기 수준을 절약하는 것도 좋습니다).

  베이스 클래스가 이미 있는 기존 컨텍스트 관리자는 `ContextDecorator`를 믹스인 클래스로 사용하여 확장할 수 있습니다:

      from contextlib import ContextDecorator

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

          def __exit__(self, *exc):
              return False

  참고

  데코레이트 된 함수는 여러 번 호출될 수 있어야 해서, 하부 컨텍스트 관리자는 여러 `with` 문에서의 사용을 지원해야 합니다. 그렇지 않으면, 함수 내에 명시적인 `with` 문이 있는 원래 구문을 사용해야 합니다.

  When the decorated callable is a generator function, coroutine function, or asynchronous generator function, the returned wrapper is of the same kind and keeps the context manager open for the lifetime of the iteration or await rather than only for the call that creates the generator or coroutine object. Wrapped generators and asynchronous generators are explicitly closed when iteration ends, as if by `closing()` or `aclosing()`.

  참고

  For asynchronous generators the wrapper re-yields each value with `async for`; values sent with `asend()` and exceptions thrown with `athrow()` are not forwarded to the wrapped generator.

  Added in version 3.2.

  버전 3.15에서 변경: Decorating a generator function, coroutine function, or asynchronous generator function now keeps the context manager open across iteration or await. Previously the context manager exited as soon as the generator or coroutine object was created.

class contextlib.AsyncContextDecorator

  Similar to `ContextDecorator`, but the context manager is entered and exited with `async with`. Decorate coroutine functions and asynchronous generator functions with this class; the returned wrapper is of the same kind.

  참고

  Synchronous functions and generators are accepted, but the wrapper is always asynchronous, so the decorated callable must then be awaited or iterated with `async for`. If that change of calling convention is not intended, use `ContextDecorator` instead.

  `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.

  버전 3.15에서 변경: Decorating an asynchronous generator function now keeps the context manager open across iteration. Previously the context manager exited as soon as the generator object was created. Synchronous functions and synchronous generator functions are also now accepted, with an asynchronous wrapper returned.

class contextlib.ExitStack

  다른 컨텍스트 관리자와 정리 함수, 특히 입력 데이터에 의해 선택적이거나 다른 방식으로 구동되는 것들을 프로그래밍 방식으로 쉽게 결합할 수 있도록 설계된 컨텍스트 관리자.

  예를 들어, 일련의 파일을 다음과 같이 단일 with 문으로 쉽게 처리할 수 있습니다:

      with ExitStack() as stack:
          files = [stack.enter_context(open(fname)) for fname in filenames]
          # 목록의 뒤에 나오는 파일을 열려는 시도가
          # 예외를 발생시키더라도, with 문의 끝에서
          # 열린 모든 파일이 자동으로 닫힙니다.

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

  각 인스턴스는 인스턴스가 닫힐 때 (명시적으로 혹은 `with` 문의 끝에서 묵시적으로) 역순으로 호출되는 등록된 콜백의 스택을 유지합니다. 컨텍스트 스택 인스턴스가 가비지 수집될 때 콜백이 묵시적으로 호출되지 않음에 유의하십시오.

  이 스택 모델은 `__init__` 메서드에서 자원을 확보하는 컨텍스트 관리자(가령 파일 객체)가 올바르게 처리될 수 있도록 사용됩니다.

  등록된 콜백이 등록 순서의 역순으로 호출되므로, 여러 개의 중첩된 `with` 문이 등록된 콜백 집합과 함께 사용된 것처럼 작동합니다. 이것은 예외 처리로도 확장됩니다 - 내부 콜백이 예외를 억제하거나 바꾸면, 외부 콜백에는 갱신된 상태에 따른 인자가 전달됩니다.

  탈출 콜백의 스택을 올바르게 되감는 세부 사항을 다루는 비교적 저수준의 API입니다. 응용 프로그램 특정 방식으로 탈출 스택을 조작하는 고수준 컨텍스트 관리자에게 적절한 기반을 제공합니다.

  Added in version 3.3.

  enter_context(cm)

    새로운 컨텍스트 관리자에 진입하고 `__exit__()` 메서드를 콜백 스택에 추가합니다. 반환 값은 컨텍스트 관리자 고유의 `__enter__()` 메서드의 결과입니다.

    이러한 컨텍스트 관리자는 `with` 문의 일부로 직접 사용되었다면 일반적으로 했을 것과 마찬가지로 예외를 억제할 수 있습니다.

    버전 3.11에서 변경: Raises `TypeError` instead of `AttributeError` if cm is not a context manager.

    버전 3.15에서 변경: Added support for arbitrary descriptors `__enter__()` and `__exit__()`.

  push(exit)

    컨텍스트 관리자의 `__exit__()` 메서드를 콜백 스택에 추가합니다.

    `__enter__`가 호출되지 않기 때문에, 이 메서드를 사용하여 컨텍스트 관리자 고유의 `__exit__()` 메서드로 `__enter__()` 구현의 일부를 보호(cover)하는 데 사용할 수 있습니다.

    컨텍스트 관리자가 아닌 객체를 전달하면, 이 메서드는 해당 객체가 컨텍스트 관리자의 `__exit__()` 메서드와 같은 서명을 가진 콜백인 것으로 가정하여 콜백 스택에 직접 추가합니다.

    참값을 반환함으로써, 이 콜백은 컨텍스트 관리자 `__exit__()` 메서드와 같은 방식으로 예외를 억제할 수 있습니다.

    전달된 객체는 함수에서 반환되어, 이 메서드를 함수 데코레이터로 사용할 수 있도록 합니다.

    버전 3.15에서 변경: Added support for arbitrary descriptors `__exit__()`.

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

    임의의 콜백 함수와 인자를 받아서 콜백 스택에 추가합니다.

    다른 메서드와 달리, 이 방법으로 추가된 콜백은 예외를 무시할 수 없습니다 (예외 세부 정보가 전달되지 않기 때문입니다).

    전달된 콜백은 함수에서 반환되어, 이 메서드를 함수 데코레이터로 사용할 수 있도록 합니다.

  pop_all()

    콜백 스택을 새로운 `ExitStack` 인스턴스로 옮기고 그것을 반환합니다. 이 작업으로 아무런 콜백도 호출되지 않습니다 - 대신, 이제 새 스택이 닫힐 때 (명시적으로나 `with` 문의 끝에서 묵시적으로) 호출됩니다.

    예를 들어, 파일 그룹을 다음과 같이 “전부 아니면 아무것도” 방식으로 열 수 있습니다:

        with ExitStack() as stack:
            files = [stack.enter_context(open(fname)) for fname in filenames]
            # close 메서드를 잡고 있지만, 아직 호출하지 않습니다.
            close_files = stack.pop_all().close
            # 어떤 파일을 여는 데 실패하면, 이전에 열었던 모든 파일이
            # 자동으로 닫힙니다. 모든 파일이 성공적으로 열리면,
            # with 문이 끝난 후에도 열린 상태로 유지됩니다. 그런 다음
            # close_files()를 명시적으로 호출하여 모두 닫을 수 있습니다.

  close()

    콜백 스택을 즉시 되감고, 등록 역순으로 콜백을 호출합니다. 등록된 모든 컨텍스트 관리자와 탈출 콜백에 전달되는 인자는 예외가 발생하지 않았음을 나타냅니다.

class contextlib.AsyncExitStack

  `ExitStack`과 유사한 비동기 컨텍스트 관리자, 코루틴 정리 로직을 가질 뿐만 아니라 동기와 비동기 컨텍스트 관리자의 결합을 지원합니다.

  `close()` 메서드는 구현되지 않았습니다; 대신 `aclose()`를 사용해야 합니다.

  async enter_async_context(cm)

    `ExitStack.enter_context()`와 유사하지만, 비동기 컨텍스트 관리자를 기대합니다.

    버전 3.11에서 변경: Raises `TypeError` instead of `AttributeError` if cm is not an asynchronous context manager.

    버전 3.15에서 변경: Added support for arbitrary descriptors `__aenter__()` and `__aexit__()`.

  push_async_exit(exit)

    `ExitStack.push()`와 유사하지만, 비동기 컨텍스트 관리자나 코루틴 함수를 기대합니다.

    버전 3.15에서 변경: Added support for arbitrary descriptors `__aexit__()`.

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

    `ExitStack.callback()`과 유사하지만 코루틴 함수를 기대합니다.

  async aclose()

    `ExitStack.close()`와 유사하지만 어웨이터블을 올바르게 처리합니다.

  `asynccontextmanager()` 에 대한 예제를 계속합니다:

      async with AsyncExitStack() as stack:
          connections = [await stack.enter_async_context(get_connection())
              for i in range(5)]
          # 목록의 뒤에서 연결을 열려는 시도가 예외를
          # 발생시키더라도, async with 문의 끝에서
          # 열린 모든 연결이 자동으로 해제됩니다.

  Added in version 3.7.

## 예제와 조리법

This section describes some examples and recipes for making effective use of the tools provided by `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)
        # 획득한 자원들을 사용하는 작업을 수행합니다

볼 수 있듯이, `ExitStack`을 사용하면 `with` 문을 사용하여 컨텍스트 관리자 프로토콜을 스스로 지원하지 않는 임의의 자원을 쉽게 관리할 수 있습니다.

### `__enter__` 메서드에서 발생하는 예외 잡기

It is occasionally desirable to catch exceptions from an `__enter__()` method implementation, without inadvertently catching exceptions from the `with` statement body or the context manager’s `__exit__()` method. By using `ExitStack` the steps in the context management protocol can be separated slightly in order to allow this:

    stack = ExitStack()
    try:
        x = stack.enter_context(cm)
    except Exception:
        # __enter__ 예외를 처리합니다
    else:
        with stack:
            # 정상적인 경우를 처리합니다

실제로 이렇게 할 필요가 있다는 것은 하부 API가 `try`/`except`/`finally` 문과 함께 사용하기 위한 직접 자원 관리 인터페이스를 제공해야 함을 나타내지만, 모든 API가 이런 측면에서 잘 설계된 것은 아닙니다. 컨텍스트 관리자가 유일하게 제공되는 자원 관리 API일 때, `ExitStack`을 사용하면 `with` 문에서 직접 처리할 수 없는 다양한 상황을 더 쉽게 처리할 수 있습니다.

### `__enter__` 구현에서 정리하기

`ExitStack.push()` 설명서에서 언급했듯이, 이 메서드는 `__enter__()` 구현의 후반 단계가 실패하면 이미 할당된 자원을 정리하는 데 유용할 수 있습니다.

다음은 선택적 유효성 검증 함수와 함께 자원 확보와 해제 함수를 받아들이고 이를 컨텍스트 관리 프로토콜에 매핑하는 컨텍스트 관리자를 위해 이를 수행하는 예제입니다:

    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
                # 유효성 검사가 통과되어 예외가 발생하지 않았습니다
                # 따라서, 자원을 유지하고 호출자에게 다시
                # 전달하려고 합니다
                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):
            # 자원 해제 로직을 중복할 필요가 없습니다
            self.release_resource()

### `try-finally`와 플래그 변수 사용 교체하기

때때로 보이는 패턴은 `finally` 절의 본문을 실행할지를 나타내는 플래그 변수가 있는 `try-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()

이를 통해 별도의 플래그 변수를 요구하지 않고, 의도하는 정리 동작을 사전에 명시적으로 만들 수 있습니다.

특정 응용 프로그램에서 이 패턴을 많이 사용한다면, 작은 도우미 클래스를 사용하여 훨씬 더 단순화할 수 있습니다:

    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()

데코레이터 프로토콜의 작동 방식으로 인해, 이 방법으로 선언된 콜백 함수는 아무런 매개 변수도 취할 수 없습니다. 대신 해제할 모든 자원은 클로저(closure) 변수로 액세스해야 합니다.

### 함수 데코레이터로 컨텍스트 관리자 사용하기

`ContextDecorator`를 사용하면 일반적인 `with` 문과 함수 데코레이터 모두로 컨텍스트 관리자를 사용할 수 있습니다.

예를 들어, 진입 시간과 탈출 시간을 추적할 수 있는 로거(logger)로 함수나 문장 그룹을 감싸는 것이 유용할 때가 있습니다. 작업에 대한 함수 데코레이터와 컨텍스트 관리자를 모두 작성하는 대신, `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()

컨텍스트 관리자를 함수 데코레이터로 사용할 때 한 가지 추가 제한 사항이 있음에 유의하십시오: `__enter__()`의 반환 값에 액세스 할 수 있는 방법이 없습니다. 이 값이 필요하면, 여전히 명시적인 `with` 문을 사용할 필요가 있습니다.

더 보기

PEP 343- “with” 문

  파이썬 `with` 문의 명세, 배경 및 예

## 일회용, 재사용 가능 및 재진입 가능 컨텍스트 관리자

대부분의 컨텍스트 관리자는 `with` 문에서 한 번만 효과적으로 사용할 수 있다는 것을 의미하는 방식으로 작성됩니다. 이러한 일회용 컨텍스트 관리자는 사용될 때마다 새로 만들어야 합니다 - 두 번째로 사용하려고 하면 예외가 발생하거나 올바르게 작동하지 않습니다.

이 흔한 제한 사항은 일반적으로 컨텍스트 관리자가 사용되는 `with` 문의 헤더에서 컨텍스트 관리자를 직접 만들도록 권고하게 합니다 (위의 모든 사용 예제에서 보이듯이).

파일은 효과적인 일회용 컨텍스트 관리자의 예입니다, 첫 번째 `with` 문이 파일을 닫아서, 해당 파일 객체를 사용하는 추가 IO 연산을 막기 때문입니다.

`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`은 `suppress()`, `redirect_stdout()` 및 `chdir()`과 같이 재진입 가능 컨텍스트 관리자의 예입니다. 재진입 사용의 매우 간단한 예는 다음과 같습니다:

    >>> 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`을 다른 스트림으로 연결하여 시스템 상태를 전역적으로 수정하므로 명백히 스레드 안전하지 않습니다.

### 재사용 가능 컨텍스트 관리자

일회용과 재진입 가능 컨텍스트 관리자와 구별되는 “재사용할 수 있는” 컨텍스트 관리자입니다 (또는 재진입 가능 컨텍스트 관리자도 재사용 가능하므로, 완전히 명시적이려면 “재사용할 수 있지만 재진입할 수 없는” 컨텍스트 관리자입니다). 이러한 컨텍스트 관리자는 여러 번 사용되는 것을 지원하지만, 같은 컨텍스트 관리자 인스턴스가 포함하는 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