Ответ
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.