C.W.K.
Stream
Lesson 05 of 06 · published

contextlib — Generator 를 context manager 로

~18 min · contextlib, contextmanager, generator, suppress

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

contextlib.contextmanager — 더 쉬운 길

__enter__/__exit__ 가진 클래스 짜는 게 단순 케이스엔 장황. contextlib.contextmanager 가 generator 를 context manager 로. yield 전 코드가 setup. yield 된 값이 as 로 바인딩. yield 후 코드가 teardown. 본문 예외가 yield 통해 전파 — try/finally 로 감싸 정리 보장.

모양 — yield 하나 + 안전을 위한 try/finally

정석 모양 — setup, 그 다음 try: yield value; finally: teardown. try 가 본문 raise 해도 teardown 실행 보장. 없으면 본문 예외가 teardown skip — context manager 가 막으려고 만들어진 부류 버그.

contextlib.suppress — 삼키는 패턴 깔끔히

with contextlib.suppress(FileNotFoundError): 가 그 예외 타입 삼키고 블록 빠져나옴. "이 특정 에러 무시" 패턴엔 try/except/pass 보다 깔끔, 특히 비자명한 정리 path 에서.

contextlib.ExitStack — 동적으로 많은 컨텍스트 합성

가끔 컴파일 타임에 컨텍스트 몇 개 진입할지 모를 때 — 열 파일 list, 락 가변 개수. ExitStack 이 다른 컨텍스트를 가면서 스택에 push 가능한 context manager, 빠질 때 다 풀어. 깊게 중첩하는 우회 대체.

Pythonic Way: setup/teardown 이 generator 에 들어갈 만큼 단순하면 클래스 기반보다 @contextmanager. __enter__ 호출 간 살아남는 state 필요하거나 더 풍부한 인터페이스 (재진입, 설정) 원하면 클래스 형태.

Code

@contextmanager — generator 를 context manager 로·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 된 값 가진 @contextmanager·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
contextlib.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")
contextlib.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())
contextlib.closing — .close() 가진 모든 거·python
import contextlib
import urllib.request

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

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

External links

Exercise

(a) @contextmanager silenced(streams=('stdout',)) — 블록 동안 stdout (옵션으로 stderr) 를 StringIO 로 리다이렉트, 빠질 때 캡쳐된 거 출력. (b) contextlib.ExitStack 사용 버전 — paths: list[str] 의 모든 path 열고 각 파일 읽고 다 닫힘 보장. (a) 는 블록 안에서 print 좀 해서 테스트. (b) 는 진짜 파일 없으니 io.StringIO 인스턴스로 테스트.

Progress

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

댓글 0

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

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