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

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

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

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


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

제공되는 함수와 클래스:

class contextlib.AbstractContextManager

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

   Added in version 3.6.

class contextlib.AbstractAsyncContextManager

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

   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)

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

      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)

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

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

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

   Added in version 3.4.

contextlib.redirect_stderr(new_target)

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

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

   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

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

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

   "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" 문이 있는 원래 구문을 사용해야 합니
     다.

   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]
          # 목록의 뒤에 나오는 파일을 열려는 시도가
          # 예외를 발생시키더라도, 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.

   push(exit)

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

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

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

      참값을 반환함으로써, 이 콜백은 컨텍스트 관리자 "__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.

   push_async_exit(exit)

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

   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.


예제와 조리법
=============

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

때때로 "with" 문 본문이나 컨텍스트 관리자의 "__exit__" 메서드에서 발생
한 예외를 실수로 포착하지 *않으면서*, "__enter__" 메서드 구현에서 발생
하는 예외를 포착하는 것이 바람직합니다. "ExitStack"을 사용하면 이를 위
해 컨텍스트 관리자 프로토콜의 단계를 약간 분리할 수 있습니다:

   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
