contextlibwith 문 컨텍스트를 위한 유틸리티

소스 코드: 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.

많은 객체가 자체적으로 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가 등장한 지점에서 다시 발생합니다. 따라서, tryexceptfinally 문을 사용하여 에러(있다면)를 잡거나, 어떤 정리가 수행되도록 할 수 있습니다. 예외를 단지 로그 하기 위해 또는 (예외를 완전히 억제하는 대신) 어떤 작업을 수행하기 위해 예외를 잡았다면 제너레이터는 해당 예외를 다시 발생시켜야 합니다. 그렇지 않으면 제너레이터 컨텍스트 관리자가 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

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

If the code within the with block raises a BaseExceptionGroup, suppressed exceptions are removed from the group. If any exceptions in the group are not suppressed, a group containing them is re-raised.

버전 3.4에 추가.

버전 3.12에서 변경: suppress now supports suppressing exceptions raised as part of an BaseExceptionGroup.

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__는 데코레이터로 사용될 때도 선택적 예외 처리를 유지합니다.

ContextDecoratorcontextmanager()에서 사용되므로, 이 기능을 자동으로 얻게 됩니다.

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

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

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

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