본문 바로가기
C.W.K.
Stream
Lesson 05 of 06 · published

contextlib — 간단한 수명 경계를 제너레이터로 쓰기

~18 min · contextlib, contextmanager, generator, suppress

Level 0호기심
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

yield 앞은 준비, 뒤는 정리

@contextmanager는 정확히 한 번 yield하는 제너레이터를 컨텍스트 관리자로 바꿔. yield한 값은 as에 묶이고, with 본문의 예외는 yield 자리로 들어오므로 정리는 try/finally에 둬야 해.

표준 도구로 흔한 모양을 줄여

suppress는 의도한 특정 예외만 무시하고, ExitStack은 실행 중 개수가 정해지는 여러 컨텍스트를 쌓아 역순으로 풀어. closing은 close 메서드만 가진 객체에 수명 경계를 줘.

정리 중 본문 예외를 잡아 삼키면 class 기반 __exit__가 참을 돌린 것과 같으니, 억제가 목적이 아니라면 그대로 전파해.

Code

제너레이터로 만드는 컨텍스트 관리자·python
import contextlib
import time

@contextlib.contextmanager
def timer(label):
    start = time.perf_counter()
    try:
        yield                              # 제어가 with 블록으로
    finally:
        elapsed = time.perf_counter() - start
        print(f"{label}: {elapsed*1000:.2f}ms")

with timer("백만 합"):
    sum(range(1_000_000))
# 백만 합: 12.34ms
yield한 값을 as로 전달하기·python
import contextlib

@contextlib.contextmanager
def temp_setting(d, key, value):
    """d[key]=value 임시 설정, 빠질 때 복원."""
    original = d.get(key)
    had_key = key in d
    d[key] = value
    try:
        yield d                            # `as` 로 바인딩
    finally:
        if had_key:
            d[key] = original
        else:
            del d[key]

config = {"debug": False}
with temp_setting(config, "debug", True) as cfg:
    print("안:", cfg["debug"])         # 안: True
print("밖:", config.get("debug"))     # 밖: False
특정 예외만 suppress하기·python
import contextlib
import os

# 옛 방법 — 장황
try:
    os.remove("maybe.txt")
except FileNotFoundError:
    pass

# 새 방법 — 선언적
with contextlib.suppress(FileNotFoundError):
    os.remove("maybe.txt")

# 여러 타입
with contextlib.suppress(FileNotFoundError, PermissionError):
    os.remove("/protected/path.txt")
실행 중 정해지는 여러 컨텍스트를 ExitStack으로 관리하기·python
import contextlib
import io

# N 파일 열기 (N 은 런타임에만 알아)
file_specs = ["a", "b", "c"]            # N 항목
fake_files = {name: io.StringIO(f"{name} 내용") for name in file_specs}

with contextlib.ExitStack() as stack:
    files = [stack.enter_context(fake_files[n]) for n in file_specs]
    # 이제 모든 파일 열림 — 빠질 때 다 깨끗히 닫힘
    for f, name in zip(files, file_specs):
        print(name, f.read())
close 메서드가 있는 객체를 with로 묶기·python
import contextlib
import urllib.request

# urlopen 은 .close() 있는 거 반환 — 3.0 전엔 context manager가 아니었음
with contextlib.closing(urllib.request.urlopen("https://example.com")) as r:
    data = r.read()
# r.close() 자동 호출

# .close() 있지만 CM 아닌 모든 리소스 감싸기에 유용

External links

Exercise

stdout과 선택적으로 stderr를 StringIO로 돌렸다가 복원하는 silenced를 @contextmanager로 만들어. 이어서 ExitStack으로 전달된 여러 파일 모양 객체를 모두 열고 읽은 뒤 반드시 닫는 버전을 작성해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.