"contextlib" --- "with" 문 컨텍스트를 위한 유틸리티
***************************************************

**소스 코드:** Lib/contextlib.py

======================================================================

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


유틸리티
========

제공되는 함수와 클래스:

class contextlib.AbstractContextManager

   "object.__enter__()"와 "object.__exit__()"를 구현하는 클래스의 *추
   상 베이스 클래스*. "self"를 반환하는 "object.__enter__()"의 기본 구
   현이 제공되지만 "object.__exit__()"는 기본적으로 "None"을 반환하는
   추상 메서드입니다. 컨텍스트 관리자 형 의 정의도 참조하십시오.

   버전 3.6에 추가.

class contextlib.AbstractAsyncContextManager

   "object.__aenter__()"와 "object.__aexit__()"를 구현하는 클래스의 *
   추상 베이스 클래스*. "self"를 반환하는 "object.__aenter__()"의 기본
   구현이 제공되지만 "object.__aexit__()"는 기본적으로 "None"을 반환하
   는 추상 메서드입니다. 비동기 컨텍스트 관리자의 정의도 참조하십시오.

   버전 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.

   While many objects natively support use in with statements,
   sometimes a resource needs to be managed that isn't a context
   manager in its own right, and doesn't implement a "close()" method
   for use with "contextlib.closing"

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

      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

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

   제너레이터가 산출하는 지점에서, "with" 문에 중첩된 블록이 실행됩니
   다. 그런 다음 블록이 종료된 후 제너레이터가 재개합니다. 블록에서 처
   리되지 않은 예외가 발생하면, 제너레이터 내부의 yield가 등장한 지점
   에서 다시 발생합니다. 따라서, "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 ...')

      버전 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():
             # ... async code ...

      When used as a decorator, a new generator instance is implicitly
      created on each function call. This allows the otherwise "one-
      shot" context managers created by "asynccontextmanager()" to
      meet the requirement that context managers support multiple
      invocations in order to be used as decorators.

   버전 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)

   Return an async context manager that calls the "aclose()" method of
   *thing* upon completion of the block.  This is basically equivalent
   to:

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

   버전 3.10에 추가.

contextlib.nullcontext(enter_result=None)

   "__enter__"에서 *enter_result*를 반환하지만, 그 외에는 아무것도 하
   지 않는 컨텍스트 관리자를 반환합니다. 선택적 컨텍스트 관리자를 위한
   대체로 사용하기 위한 것입니다, 예를 들어:

      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

   It can also be used as a stand-in for asynchronous context
   managers:

      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

   버전 3.7에 추가.

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

contextlib.suppress(*exceptions)

   Return a context manager that suppresses any of the specified
   exceptions if they occur in the body of a "with" statement and then
   resumes execution with the first statement following the end of the
   "with" statement.

   예외를 완전히 억제하는 다른 메커니즘과 마찬가지로, 이 컨텍스트 관리
   자는 프로그램 실행을 조용히 계속하는 것이 옳은 것으로 알려진 매우
   특정한 에러를 다루기 위해서만 사용해야 합니다.

   예를 들면:

      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

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

   버전 3.4에 추가.

contextlib.redirect_stdout(new_target)

   "sys.stdout"을 다른 파일이나 파일류 객체로 일시적으로 리디렉션 하기
   위한 컨텍스트 관리자.

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

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

   버전 3.4에 추가.

contextlib.redirect_stderr(new_target)

   "redirect_stdout()"과 유사하지만, "sys.stderr"를 다른 파일이나 파일
   류 객체로 리디렉션 합니다.

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

   버전 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.

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

   버전 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"
   를 믹스인 클래스로 사용하여 확장할 수 있습니다:

      from contextlib import ContextDecorator

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

          def __exit__(self, *exc):
              return False

   참고:

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

   버전 3.2에 추가.

class contextlib.AsyncContextDecorator

   Similar to "ContextDecorator" but only for asynchronous functions.

   Example of "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

   버전 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입니다. 응용 프로그램 특정 방식으로 탈출 스택을 조작하는 고수
   준 컨텍스트 관리자에게 적절한 기반을 제공합니다.

   버전 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.

   버전 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__" 메서드에서 발생하는 예외 잡기
-----------------------------------------

때때로 "with" 문 본문이나 컨텍스트 관리자의 "__exit__" 메서드에서 발생
한 예외를 실수로 포착하지 *않으면서*, "__enter__" 메서드 구현에서 발생
하는 예외를 포착하는 것이 바람직합니다. "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"와 플래그 변수 사용 교체하기
-----------------------------------------

때때로 보이는 패턴은 "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()

This allows the intended cleanup up 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()

데코레이터 프로토콜의 작동 방식으로 인해, 이 방법으로 선언된 콜백 함수
는 아무런 매개 변수도 취할 수 없습니다. 대신 해제할 모든 자원은 클로저
(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()

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


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

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