context manager
연결된 노트와 함께 읽기
- Context Manager =
with 문에 쓰는 객체. 진입(__enter__)에서 자원을 얻고, 종료(__exit__)에서 정확히 해제되는 RAII 패턴.
- 에이전트 코드에서 자주 등장 — 트레이싱 span, DB 트랜잭션, 파일/소켓, 임시 환경변수, 비동기 클라이언트.
기본 사용
직접 구현 — 클래스 방식
contextlib.contextmanager — 함수형
비동기 — async with
- asyncio 친화.
__aenter__ / __aexit__를 구현하거나 @asynccontextmanager 사용.
트레이싱·관측 — Span as Context Manager
DB 트랜잭션
다중 자원 — ExitStack
흔한 함정
__exit__에서 True를 반환하면 예외가 삼켜진다 — 의도한 게 아니면 항상 False.
with 안에서 return해도 __exit__는 실행됨 (정상).
- 비동기 컨텍스트를 동기
with로 쓰면 동작 안 함.
관련
with open("file.txt") as f: # __enter__: open, __exit__: close
data = f.read()
# 여기서 f는 자동 close
class Timer:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, exc_type, exc, tb):
self.elapsed = time.time() - self.start
return False # 예외 재발생
with Timer() as t:
do_work()
print(t.elapsed)
from contextlib import contextmanager
@contextmanager
def timer(label):
start = time.time()
try:
yield
finally:
print(f"{label}: {time.time()-start:.2f}s")
with timer("llm_call"):
llm.invoke(prompt)
import httpx
async def fetch():
async with httpx.AsyncClient() as client:
return await client.get("...")
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("agent.invoke") as span:
span.set_attribute("user_id", uid)
result = agent.invoke(...)
with db.session() as s:
s.execute("INSERT ...")
# 예외 없으면 commit, 있으면 rollback
from contextlib import ExitStack
with ExitStack() as stack:
files = [stack.enter_context(open(p)) for p in paths]
# 모든 파일이 끝에 함께 닫힘