2.24. 컨텍스트 관리자

with 문은 설정 코드, 본문, 그리고 정리 코드를 순서대로 실행합니다 – 본문이 도중에 실패하더라도 정리 코드가 반드시 실행된다는 보장과 함께 말이죠. 설정과 정리를 제공하는 한 쌍의 메서드를 컨텍스트 관리자 라고 합니다.

형태는 다음과 같습니다:

with <expression> as <name>:
    <body>

표현식은 컨텍스트 관리자를 반환합니다. Python은 그것의 __enter__ 메서드를 호출하고, 선택적으로 결과를 <name> 에 바인딩한 다음 본문을 실행하고, 그 후 __exit__ 를 호출합니다 – 본문이 정상적으로 완료되었든 예외를 발생시켰든 상관없이 말이죠.

흐름도: __enter__가 먼저 실행되고, 그다음 본문이 실행됩니다. 본문은 정상적으로 완료되거나 예외를 발생시킵니다. 어느 경우든 __exit__가 마지막에 실행됩니다.

__exit__ 는 블록 안에서 무슨 일이 일어나든 블록의 끝에서 실행됩니다.

2.24.1. 컨텍스트 관리자 사용하기

대표적인 예는 파일 열기입니다:

with open("data.txt") as f:
    text = f.read()

# f is now closed, even if read() failed

open() 은 그 자체로 컨텍스트 관리자인 파일 객체를 반환합니다. __enter__ 는 파일을 반환하고 __exit__ 는 파일을 닫습니다. with 블록은 “끝나면 항상 파일을 닫는다”를 호출자가 기억해야 하는 것이 아니라 기본 동작으로 만듭니다.

2.24.1.1. 여러 개의 컨텍스트 관리자

하나의 with 문은 쉼표로 구분하여 여러 컨텍스트 관리자를 한 번에 진입할 수 있습니다:

with open("input.txt") as src, open("output.txt", "w") as dst:
    dst.write(src.read())

두 개의 중첩된 with 블록과 동등하지만 더 평평합니다. 관리자들은 왼쪽에서 오른쪽으로 진입하고 역순으로 종료됩니다. 오른쪽 관리자의 __enter__ 가 예외를 발생시키면 왼쪽 관리자의 __exit__ 는 여전히 실행됩니다.

2.24.2. 컨텍스트 관리자 작성하기

__enter____exit__ 메서드를 가진 모든 클래스는 컨텍스트 관리자로 작동합니다:

class Section:
    def __init__(self, label):
        self.label = label

    def __enter__(self):
        print("---", self.label, "---")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("--- end", self.label, "---")
        return False

with Section("setup"):
    print("doing the work")

출력:

--- setup ---
doing the work
--- end setup ---

__exit__ 는 블록을 끝낸 예외를 설명하는 세 개의 인수를 받거나, 블록이 정상적으로 완료된 경우 세 개의 None 값을 받습니다. False (또는 None)를 반환하면 정리 후 모든 예외가 전파되도록 하고, True 를 반환하면 예외를 삼킵니다.

“열기 / 닫기” 또는 “획득 / 해제” 생명주기를 가진 모든 리소스에 컨텍스트 관리자를 사용하세요 – 파일에만 국한되지 않습니다. 이 패턴은 정리를 설정과 짝지어 둘 다 도입되는 지점에 두므로, 긴 함수 중간에서 닫기를 잊더라도 리소스가 누수될 수 없습니다.