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
¶ 이 함수는 클래스나 별도의
__enter__()
와__exit__()
메서드를 작성할 필요 없이,with
문 컨텍스트 관리자를 위한 팩토리 함수를 정의하는 데 사용할 수 있는 데코레이터입니다.많은 객체가 자체적으로 with 문에서의 사용을 지원하지만, 스스로는 컨텍스트 관리자가 아니고
contextlib.closing
과 함께 사용할close()
메서드를 구현하지 않는 자원을 관리해야 하는 경우가 있습니다.올바른 자원 관리를 보장하기 위한 추상적인 예는 다음과 같습니다:
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()
와 유사하지만, 비동기 컨텍스트 관리자를 만듭니다.이 함수는 클래스나 별도의
__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 ...')
버전 3.7에 추가.
Context managers defined with
asynccontextmanager()
can be used either as decorators or withasync 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()
가 호출됩니다.
-
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 bybreak
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 thewith
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 anio.StringIO
object. The replacement stream is returned from the__enter__
method and so is available as the target of thewith
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에 추가.
-
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 theExitStack
instance, and performs no additional operations.각 인스턴스는 인스턴스가 닫힐 때 (명시적으로 혹은
with
문의 끝에서 묵시적으로) 역순으로 호출되는 등록된 콜백의 스택을 유지합니다. 컨텍스트 스택 인스턴스가 가비지 수집될 때 콜백이 묵시적으로 호출되지 않음에 유의하십시오.이 스택 모델은
__init__
메서드에서 자원을 확보하는 컨텍스트 관리자(가령 파일 객체)가 올바르게 처리될 수 있도록 사용됩니다.등록된 콜백이 등록 순서의 역순으로 호출되므로, 여러 개의 중첩된
with
문이 등록된 콜백 집합과 함께 사용된 것처럼 작동합니다. 이것은 예외 처리로도 확장됩니다 - 내부 콜백이 예외를 억제하거나 바꾸면, 외부 콜백에는 갱신된 상태에 따른 인자가 전달됩니다.탈출 콜백의 스택을 올바르게 되감는 세부 사항을 다루는 비교적 저수준의 API입니다. 응용 프로그램 특정 방식으로 탈출 스택을 조작하는 고수준 컨텍스트 관리자에게 적절한 기반을 제공합니다.
버전 3.3에 추가.
-
enter_context
(cm)¶ 새로운 컨텍스트 관리자에 진입하고
__exit__()
메서드를 콜백 스택에 추가합니다. 반환 값은 컨텍스트 관리자 고유의__enter__()
메서드의 결과입니다.이러한 컨텍스트 관리자는
with
문의 일부로 직접 사용되었다면 일반적으로 했을 것과 마찬가지로 예외를 억제할 수 있습니다.
-
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] # 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
과 유사한 비동기 컨텍스트 관리자, 코루틴 정리 로직을 가질 뿐만 아니라 동기와 비동기 컨텍스트 관리자의 결합을 지원합니다.close()
메서드는 구현되지 않으며, 대신aclose()
를 사용해야 합니다.-
coroutine
enter_async_context
(cm)¶ enter_context()
와 유사하지만, 비동기 컨텍스트 관리자를 기대합니다.
-
push_async_exit
(exit)¶ push()
와 유사하지만, 비동기 컨텍스트 관리자나 코루틴 함수를 기대합니다.
-
push_async_callback
(callback, /, *args, **kwds)¶ callback()
과 유사하지만 코루틴 함수를 기대합니다.
-
coroutine
aclose
()¶ close()
와 유사하지만 어웨이터블을 올바르게 처리합니다.
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에 추가.
-
coroutine
예제와 조리법¶
이 섹션에서는 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__
구현에서 정리하기¶
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
# 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()
이를 통해 별도의 플래그 변수를 요구하지 않고, 의도하는 정리 동작을 사전에 명시적으로 만들 수 있습니다.
특정 응용 프로그램에서 이 패턴을 많이 사용한다면, 작은 도우미 클래스를 사용하여 훨씬 더 단순화할 수 있습니다:
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
문을 사용할 필요가 있습니다.
일회용, 재사용 가능 및 재진입 가능 컨텍스트 관리자¶
대부분의 컨텍스트 관리자는 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()
과 같이 재진입 가능 컨텍스트 관리자의 예입니다. 재진입 사용의 매우 간단한 예는 다음과 같습니다:
>>> 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