← Назад к вопросам

Что такое contextvars в Python и для чего они используются?

3.0 Senior🔥 101 комментариев
#FastAPI и Flask#Git и VCS

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Contextvars в Python

Contextvars — это модуль Python (введён в версии 3.7), который предоставляет механизм для управления переменными контекста, специфичными для асинхронных задач и потоков. Это решение для проблемы, когда нужно хранить данные, которые логически принадлежат определённому контексту выполнения, но не могут быть переданы как параметры через весь стек вызовов.

Основные концепции

Проблема, которую решает contextvars

В многопоточных и асинхронных приложениях часто нужно сохранять контекстную информацию (user_id, request_id, трейсинг-данные). Обычные глобальные переменные в этом случае небезопасны:

# ❌ Плохо — не потокобезопасно
global_user_id = None

def process_request(user_id):
    global global_user_id
    global_user_id = user_id
    result = some_operation()
    return result

В многопоточной среде разные потоки перепишут друг другу значение.

Правильное решение с contextvars

import contextvars

# Создаём контекстную переменную
user_id_var = contextvars.ContextVar("user_id", default=None)

def process_request(user_id):
    token = user_id_var.set(user_id)  # Установливаем значение
    try:
        result = some_operation()
        return result
    finally:
        user_id_var.reset(token)  # Восстанавливаем прежнее значение

def some_operation():
    # Получаем значение из контекста, без передачи как параметра
    current_user = user_id_var.get()
    print(f"Processing for user: {current_user}")

Применение в асинхронном коде

Contextvars особенно полезны в asyncio, где каждая задача имеет свой контекст:

import asyncio
import contextvars

request_id = contextvars.ContextVar("request_id")

async def fetch_data(user_id):
    request_id.set(f"req-{user_id}-{id(asyncio.current_task())}")
    await asyncio.sleep(1)
    print(f"Request ID: {request_id.get()}")

async def main():
    # Каждая задача имеет свой контекст
    await asyncio.gather(
        fetch_data(1),
        fetch_data(2),
        fetch_data(3),
    )

Практическое применение

В веб-фреймворках (FastAPI, Flask)

from fastapi import FastAPI, Request
import contextvars
import logging

app = FastAPI()
request_id = contextvars.ContextVar("request_id", default=None)

@app.middleware("http")
async def add_request_id(request: Request, call_next):
    req_id = request.headers.get("X-Request-ID", str(uuid.uuid4()))
    token = request_id.set(req_id)
    
    # Логи автоматически могут использовать request_id
    response = await call_next(request)
    request_id.reset(token)
    return response

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    # Получаем request_id без параметров
    req_id = request_id.get()
    logger.info(f"Fetching user {user_id}, request: {req_id}")
    return {"user_id": user_id}

В логировании

import logging
import contextvars

user_context = contextvars.ContextVar("user", default="anonymous")

class ContextFilter(logging.Filter):
    def filter(self, record):
        record.user = user_context.get()
        return True

logger = logging.getLogger(__name__)
logger.addFilter(ContextFilter())

formatter = logging.Formatter("%(asctime)s - %(user)s - %(message)s")
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)

user_context.set("john_doe")
logger.info("User action performed")  # Выведет: User action performed for john_doe

Копирование контекста

Есть также методы для копирования контекста при создании новых потоков или задач:

import contextvars
import asyncio

var = contextvars.ContextVar("var")

async def main():
    var.set("original")
    
    # Скопируем контекст
    ctx = contextvars.copy_context()
    
    # Запустим функцию в копированном контексте
    result = await asyncio.to_thread(blocking_func)

Ключевые преимущества

  • Потокобезопасность: Каждый поток/задача имеет свою копию значения
  • Чистота кода: Не нужно передавать контекст через все функции
  • Встроенная поддержка asyncio: Автоматически работает с асинхронным кодом
  • Trace-ID и Logging: Идеально для распределённого трейсинга
  • Type-safe: Используют типизацию через ContextVar[T]
Что такое contextvars в Python и для чего они используются? | PrepBro