Какую проблему решает модуль contextvars в Python и в чем его преимущество перед threading.local

Ответ

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

Проблема с threading.local в asyncio

В asyncio несколько корутин (задач) могут выполняться в одном и том же потоке операционной системы. threading.local хранит данные, уникальные для потока, поэтому все корутины в этом потоке будут видеть одни и те же данные, что приводит к их смешиванию и некорректному поведению.

contextvars решает эту проблему, так как переменные, созданные с его помощью, привязаны к контексту выполнения (context), а не к потоку. Каждая задача asyncio имеет свой собственный контекст.

Как это работает

import asyncio
import contextvars

# Создаем контекстную переменную с именем и значением по умолчанию
request_id_var = contextvars.ContextVar('request_id', default='N/A')

async def process_request(req_id):
    # Устанавливаем значение переменной для текущего контекста (задачи)
    token = request_id_var.set(req_id)
    print(f"[{request_id_var.get()}] Начало обработки...")
    await asyncio.sleep(0.1)
    await log_event()
    # Восстанавливаем предыдущее значение переменной
    request_id_var.reset(token)

async def log_event():
    # Вложенная функция имеет доступ к значению из контекста
    print(f"[{request_id_var.get()}] Произошло событие в логе.")

async def main():
    await asyncio.gather(
        process_request('req-1'),
        process_request('req-2')
    )

asyncio.run(main())

Вывод:

[req-1] Начало обработки...
[req-2] Начало обработки...
[req-1] Произошло событие в логе.
[req-2] Произошло событие в логе.

Как видно из примера, каждая задача process_request работает со своим значением request_id, даже если выполняется в одном потоке.

Основные сценарии использования:

  • Трассировка запросов (request ID, correlation ID).
  • Управление сессиями пользователей.
  • Контекст транзакций базы данных в асинхронных ORM.