프로그래밍/C, C++, Java, Python

Python contextvars 사용법

포도알77 2026. 8. 20. 12:07

contextvars는 비동기 코드에서 요청별 상태, 추적 ID, 로깅 문맥처럼 "현재 실행 흐름에만 붙어야 하는 값"을 다룰 때 쓰는 Python 표준 라이브러리입니다. 전역 변수처럼 보이지만 실제 값은 현재 Context에 묶이므로, 같은 변수 이름을 써도 실행 문맥이 다르면 다른 값을 가질 수 있습니다.

핵심 기준은 세 가지입니다. 변수를 모듈 최상위에 만들고, 값을 바꿀 때는 set()이 돌려준 Token으로 되돌리며, asyncio 작업처럼 동시 실행되는 흐름에서는 threading.local()보다 ContextVar를 우선 검토합니다.

contextvars는 무엇을 해결하나

Python 공식 문서는 contextvars가 context-local 상태를 관리하는 API라고 설명합니다. 특히 상태를 가진 context manager가 동시 실행 코드에서 값을 다른 코드로 흘리지 않게 하려면 threading.local() 대신 Context Variables를 쓰라고 안내합니다.

PEP 567도 같은 문제를 다룹니다. OS thread 하나 안에서 여러 비동기 작업이 번갈아 실행되면 thread-local 값만으로는 작업별 상태를 정확히 나누기 어렵습니다. ContextVar는 값의 범위를 thread 자체가 아니라 현재 실행 context에 붙이는 방식으로 이 문제를 줄입니다.

기본 구조는 어떻게 생겼나

가장 중요한 객체는 ContextVar, Token, Context입니다. ContextVar는 값을 꺼내고 넣는 키 역할을 하고, Token은 이전 값으로 되돌릴 때 사용합니다. Context는 여러 ContextVar와 그 값의 매핑입니다.

객체 역할 실무 기준
ContextVar 현재 context에서 값을 읽고 쓰는 키 모듈 최상위에서 선언합니다.
Token set() 이전 값으로 되돌리는 핸들 같은 token은 한 번만 reset합니다.
Context context 변수와 값의 매핑 copy_context()run()으로 다룹니다.

ContextVar는 어디에 선언해야 하나

공식 문서는 ContextVar를 top module level에서 만들고 closure 안에서 만들지 말라고 경고합니다. Context 객체가 context variable에 강한 참조를 갖기 때문에, closure 안에서 계속 새로 만들면 garbage collection이 기대대로 되지 않을 수 있습니다.

from contextvars import ContextVar

request_id = ContextVar("request_id", default="-")

값을 읽을 때는 get()을 씁니다. 현재 context에 값이 없으면 get()의 인자로 받은 기본값, ContextVar 생성 시 지정한 기본값 순서로 반환합니다. 둘 다 없으면 LookupError가 발생합니다.

current = request_id.get()
fallback = request_id.get("unknown")

set과 reset은 왜 같이 봐야 하나

ContextVar.set(value)는 현재 context에 새 값을 설정하고 Token을 반환합니다. 이 token을 ContextVar.reset(token)에 넘기면 set() 이전 값으로 되돌릴 수 있습니다.

운영 코드에서는 값을 설정한 뒤 원래 상태로 복구하는 흐름이 중요합니다. 요청 처리 중에 잠깐 trace id를 바꾸거나, 테스트에서 임시 값을 넣는 경우 reset을 빼먹으면 이후 코드가 의도하지 않은 값을 볼 수 있습니다.

from contextvars import ContextVar

request_id = ContextVar("request_id", default="-")

token = request_id.set("req-123")
try:
    assert request_id.get() == "req-123"
finally:
    request_id.reset(token)

Python 3.14 공식 문서 기준으로는 Token을 context manager처럼 사용할 수도 있습니다. 이 방식은 수동 try/finally와 같은 의도이므로, Python 3.14 이상만 대상으로 하는 코드에서는 더 짧게 쓸 수 있습니다.

with request_id.set("req-123"):
    assert request_id.get() == "req-123"

assert request_id.get() == "-"

copy_context는 언제 쓰나

copy_context()는 현재 context의 복사본을 반환합니다. 공식 문서는 이 함수의 복잡도가 O(1)이라고 설명합니다. context variable이 몇 개이든 복사 호출 자체는 같은 복잡도를 갖는다는 뜻입니다.

복사한 Contextrun(callable, *args, **kwargs)으로 실행 흐름을 감싸는 데 사용할 수 있습니다. Context.run()은 해당 context를 현재 context로 만든 뒤 함수를 실행하고, 끝나면 이전 context로 복원합니다.

import contextvars

request_id = contextvars.ContextVar("request_id", default="-")
request_id.set("req-123")

ctx = contextvars.copy_context()

def render():
    return request_id.get()

assert ctx.run(render) == "req-123"

asyncio에서는 무엇이 달라지나

Python 공식 문서는 context variables가 asyncio에서 별도 설정 없이 기본 지원된다고 설명합니다. 그래서 요청을 처리하는 task 안에서 ContextVar에 값을 넣으면, 그 task가 호출하는 하위 함수가 별도 인자를 받지 않아도 현재 context 값을 읽을 수 있습니다.

다만 이것은 값을 아무 데서나 숨겨도 된다는 뜻이 아닙니다. 함수의 핵심 입력값은 명시적 인자로 넘기는 편이 읽기 쉽습니다. ContextVar는 요청 ID, 현재 사용자 언어, 로깅 문맥처럼 여러 계층에서 참조하지만 핵심 비즈니스 인자는 아닌 값에 더 잘 맞습니다.

threading.local과 어떻게 구분하나

threading.local()은 thread별 상태가 필요할 때 맞는 도구입니다. 반면 같은 thread 안에서 여러 async task가 함께 실행되는 구조에서는 task별 상태를 나누는 목적에 ContextVar가 더 적합합니다.

상황 먼저 볼 선택지 이유
thread마다 독립 상태가 필요하다 threading.local() 상태 범위가 OS thread에 맞습니다.
async task마다 요청 문맥이 필요하다 ContextVar 같은 thread 안의 동시 작업을 분리할 수 있습니다.
함수의 핵심 입력값이다 명시적 인자 숨은 의존성보다 호출부가 분명합니다.

실무 기준

첫째, ContextVar는 모듈 최상위에서 한 번 선언합니다. 요청마다 새 변수를 만드는 방식이 아니라, 변수는 고정하고 값만 context마다 다르게 둡니다.

둘째, set()을 호출하면 복구 전략을 같이 둡니다. Python 3.14 이상에서는 with var.set(...)를 사용할 수 있고, 그보다 낮은 버전을 지원하면 try/finallyreset(token)을 명시하는 편이 안전합니다.

셋째, ContextVar를 전역 설정 저장소처럼 쓰지 않습니다. 여러 함수가 암묵적으로 읽는 값은 테스트와 디버깅을 어렵게 만들 수 있습니다. 요청 문맥처럼 범위가 분명하고, 호출 체인 전체에서 필요한 값에만 제한적으로 쓰는 편이 좋습니다.

넷째, 지원 Python 버전을 확인합니다. contextvars 자체는 Python 3.7에 추가됐지만, token을 context manager로 쓰는 방식은 Python 3.14에 추가됐습니다. 라이브러리 코드라면 이 차이를 문서화하거나 버전별 코드를 나눠야 합니다.

FAQ

ContextVar는 전역 변수인가

변수 객체는 보통 모듈 최상위에 선언하지만, 값은 현재 context에 저장됩니다. 그래서 일반 전역 변수처럼 모든 실행 흐름이 같은 값을 공유한다고 보면 안 됩니다.

ContextVar를 함수 안에서 만들면 안 되나

공식 문서는 top module level에서 만들라고 권장합니다. Context가 context variable에 강한 참조를 갖기 때문에 closure 안에서 만드는 패턴은 메모리 관리상 문제가 될 수 있습니다.

Python 3.13에서도 with var.set(...)을 쓸 수 있나

아닙니다. token을 context manager로 쓰는 기능은 Python 3.14에 추가됐습니다. Python 3.13 이하를 지원하면 token = var.set(...)reset(token) 흐름을 사용해야 합니다.

ContextVar가 있으면 함수 인자를 줄여도 되나

무조건 그렇지는 않습니다. 핵심 입력값은 명시적 인자로 넘기는 편이 좋습니다. ContextVar는 요청 ID, 로깅 문맥, 현재 언어처럼 호출 계층을 가로질러 읽히는 보조 문맥에 더 적합합니다.

참고 문서

반응형

'프로그래밍 > C, C++, Java, Python' 카테고리의 다른 글

Python compression.zstd 사용법  (0) 2026.08.17
Java Compact Object Headers 기준  (0) 2026.08.13
Python t-string 사용법  (0) 2026.08.09
Python InterpreterPoolExecutor 사용법  (0) 2026.08.04
Python Path.info 사용법  (1) 2026.08.01
페이스북으로 공유카카오톡으로 공유카카오스토리로 공유트위터로 공유URL 복사