TGViewer
Python Заметки Python Заметки @pythonotes · 2.2K subscribers
Post #429 664
Мы рассмотрели два способа управления конеткстом переменных. Если вам показалось, что это выглядит излишне и можно было бы оставить один, то вам не показалось.
Способ с threading.local придуман для разделения переменных между потоками. CоntextVar был добавлен как новый метод для асинхронного кода, но оказался настолько универсальным, что его можно использовать и с потоками.
После появления ContextVar в PEP567 его рекомендовано использовать вместо threading.local.
И даже был сделан бекпорт для версий ниже 3.7.1.

Теперь, если совместить ContextVar и Proxy-класс из прошлого примера то получим такой класс↗️.

Но у этого класса есть две проблемы:

1️⃣ Нигде не вызывается reset для сброса переменной, что может приводить проблемам

- утечка памяти
- "грязный" конеткст при переиспользовании потоков
- невозможность вернуться к дефолту

Решим это с помощью конектстного менеджера:
@contextlib.contextmanager
def configure_context(self, *args, **kwargs):
"""Синхронный контекстный менеджер (для `with`)"""
tok_cfg = self._cv_config.set((args, kwargs))
tok_obj = self._cv_object.set(None)
try:
yield self
finally:
self._cv_object.reset(tok_obj)
self._cv_config.reset(tok_cfg)

@contextlib.asynccontextmanager
async def aconfigure_context(self, *args, **kwargs):
"""Асинхронный контекстный менеджер (для `async with`)"""
tok_cfg = self._cv_config.set((args, kwargs))
tok_obj = self._cv_object.set(None)
try:
yield self
finally:
self._cv_object.reset(tok_obj)
self._cv_config.reset(tok_cfg)


Пример использования:
with proxy.configure_context(val1, val2):
proxy.do_something()


Теперь прокси готов, но...

2️⃣ В асинхронном коде, для которого и придуманы ContextVar, созданием корутин занимается Event Loop, именно он отвечает за наследование контекста дочерними корутинами. В случае с потоками ничего такого нет, мы сами себе "эвентлуп", поэтому приходится прописывать копирование конеткста самстоятельно.

Пример проблемы с отсутствием наследованием конеткста в потоках↗️

Для решения есть функция копирования текущего контекста и метод запуска функции с новым конектстом:
сontextvars.copy_context().run(func, *args, **kwargs)


Здесь сложно придумать универсальное автоматическое копирование контекста, самая простая функция будет выглядеть так:
def run_in_thread_with_context(
func: Callable, *args, **kwargs
) -> threading.Thread:
ctx = contextvars.copy_context()
t = threading.Thread(
target=lambda: ctx.run(func, *args, **kwargs)
)
t.start()
return t


И если вернуться к нашему синхронному ApiClient, то придётся следить за конектстом самостоятельно. И если где-то в коде библиотеки уже есть вызов тредов, то это работать не будет, придется переписывать.

threading.local тоже не наследует конеткст.


Полный пример Proxy с CоntextVar↗️

Пример использования:
client = ContextVarProxy(ApiClient)

def worker_in_thread(token):
with client.configure_context(token=token):
use_client(...)


Еще вариант, это кастомные ThreadExecutor и Thread с поддержкой автокопирования контекста. Забираем здесь↗️

И нет, это не пример как надо делать в проде) Это просто эксперемент для понимания процесса.

#tricks
Python Enhancement Proposals (PEPs) PEP 567 – Context Variables | peps.python.org This PEP proposes a new contextvars module and a set of new CPython C APIs to support context variables. This concept is similar to thread-local storage (TLS), but, unlike TLS, it also allows correctly keeping track of values per asynchronous task, e.g...
  • ❤ 3
  • 👍 1
  • 👏 1
More from @pythonotes
  1. Sep 21, 2026­ WebAssembly это мощная технология позволяющая загружать практически любое приложение в б…
  2. Aug 17, 2026Не так давно столкнулся с API который в ответе с ошибкой присылал только статус-код. Без п…
  3. Jul 20, 2026У меня основная ОС это Linux. Но нередко требуется делать сборку клиента под Windows. Соби…
  4. Jul 13, 2026В прошлый раз был пример с понижением привелегий процесса. Проблема в том, что обратно под…
  5. Jul 6, 2026Модуль pwd может использоваться для считывания информации о пользователях из базы данных п…
  6. Jun 22, 2026Недавно я реализовал Multi Tenancy систему на FastAPI + SQLAlchemy и пересобрал её в небол…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →