contextvars.md
1> **Источник:** https://python-all.ru/3/library/contextvars.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# `contextvars` – Контекстные переменные89---1011Этот модуль предоставляет API для управления, хранения и доступа к контекстно-локальному состоянию. Класс [`ContextVar`](https://python-all.ru/3/library/contextvars.html#contextvars.ContextVar) используется для объявления и работы с *контекстными переменными*. Функция [`copy_context()`](https://python-all.ru/3/library/contextvars.html#contextvars.copy_context) и класс [`Context`](https://python-all.ru/3/library/contextvars.html#contextvars.Context) должны использоваться для управления текущим контекстом в асинхронных фреймворках.1213Менеджеры контекста, имеющие состояние, должны использовать контекстные переменные вместо [`threading.local()`](https://python-all.ru/3/library/threading.html#threading.local), чтобы предотвратить непреднамеренную утечку своего состояния в другой код при использовании в конкурентном коде.1415См. также [**PEP 567**](https://python-all.ru/3/library/contextvars.html) для получения дополнительных сведений.1617Добавлено в версии 3.7.1819## Контекстные переменные2021#### `class contextvars.ContextVar(name[, *, default])`2223Этот класс используется для объявления новой контекстной переменной, например:2425```python26var: ContextVar[int] = ContextVar('var', default=42)27```2829Обязательный параметр *name* используется для целей интроспекции и отладки.3031Необязательный параметр *default*, передаваемый только по ключу, возвращается [`ContextVar.get()`](https://python-all.ru/3/library/contextvars.html#contextvars.ContextVar.get), если в текущем контексте не найдено значение для переменной.3233**Важно:** Контекстные переменные должны создаваться на верхнем уровне модуля, а не в замыканиях. Объекты [`Context`](https://python-all.ru/3/library/contextvars.html#contextvars.Context) содержат сильные ссылки на контекстные переменные, что препятствует их корректной сборке мусора.3435`ContextVar` являются [обобщёнными](https://python-all.ru/3/library/typing.html#generics) по типу содержащегося в них значения.3637#### `name`3839Имя переменной. Это свойство только для чтения.4041Добавлено в версии 3.7.1.4243#### `get([default])`4445Возвращает значение для контекстной переменной в текущем контексте.4647Если в текущем контексте нет значения для переменной, метод:4849- возвращает значение аргумента *default* метода, если он был передан; или50- возвращает значение по умолчанию для контекстной переменной, если она была создана с ним; или51- возбуждает [`LookupError`](https://python-all.ru/3/library/exceptions.html#LookupError).5253#### `set(value)`5455Вызов для установки нового значения контекстной переменной в текущем контексте.5657Обязательный аргумент *value* – это новое значение для контекстной переменной.5859Возвращает объект [`Token`](https://python-all.ru/3/library/contextvars.html#contextvars.Token), который можно использовать для восстановления предыдущего значения переменной с помощью метода [`ContextVar.reset()`](https://python-all.ru/3/library/contextvars.html#contextvars.ContextVar.reset).6061Для удобства объект токена можно использовать как менеджер контекста, чтобы избежать ручного вызова [`ContextVar.reset()`](https://python-all.ru/3/library/contextvars.html#contextvars.ContextVar.reset):6263```python64var = ContextVar('var', default='default value')6566with var.set('new value'):67 assert var.get() == 'new value'6869assert var.get() == 'default value'70```7172Это краткая запись для:7374```python75var = ContextVar('var', default='default value')7677token = var.set('new value')78try:79 assert var.get() == 'new value'80finally:81 var.reset(token)8283assert var.get() == 'default value'84```8586Добавлено в версии 3.14: Добавлена поддержка использования токенов в качестве менеджеров контекста.8788#### `reset(token)`8990Сбрасывает контекстную переменную до значения, которое она имела до вызова [`ContextVar.set()`](https://python-all.ru/3/library/contextvars.html#contextvars.ContextVar.set), создавшего *токен*.9192Например:9394```python95var = ContextVar('var')9697token = var.set('new value')98# код, который использует 'var'; var.get() возвращает 'new value'.99var.reset(token)100101# После вызова reset переменная снова не имеет значения, поэтому102# var.get() вызовет исключение LookupError.103```104105Один и тот же *токен* нельзя использовать дважды.106107#### `class contextvars.Token`108109Объекты *Token* возвращаются методом [`ContextVar.set()`](https://python-all.ru/3/library/contextvars.html#contextvars.ContextVar.set). Их можно передать методу [`ContextVar.reset()`](https://python-all.ru/3/library/contextvars.html#contextvars.ContextVar.reset), чтобы вернуть значение переменной к тому, каким оно было до соответствующего *set*. Один токен не может сбросить контекстную переменную более одного раза.110111Токены поддерживают [протокол менеджера контекста](https://python-all.ru/3/reference/datamodel.html#context-managers) для автоматического сброса контекстных переменных. См. [`ContextVar.set()`](https://python-all.ru/3/library/contextvars.html#contextvars.ContextVar.set).112113Токены являются [обобщёнными](https://python-all.ru/3/library/typing.html#generics) относительно того же типа, что и [`ContextVar`](https://python-all.ru/3/library/contextvars.html#contextvars.ContextVar), который их создал.114115Добавлено в версии 3.14: Добавлена поддержка использования в качестве менеджера контекста.116117#### `var`118119Свойство только для чтения. Указывает на объект [`ContextVar`](https://python-all.ru/3/library/contextvars.html#contextvars.ContextVar), который создал токен.120121#### `old_value`122123Свойство только для чтения. Установлено в значение, которое переменная имела до вызова метода [`ContextVar.set()`](https://python-all.ru/3/library/contextvars.html#contextvars.ContextVar.set), создавшего токен. Указывает на [`Token.MISSING`](https://python-all.ru/3/library/contextvars.html#contextvars.Token.MISSING), если переменная не была установлена до вызова.124125#### `MISSING`126127Объект-маркер, используемый [`Token.old_value`](https://python-all.ru/3/library/contextvars.html#contextvars.Token.old_value).128129## Ручное управление контекстом130131#### `contextvars.copy_context()`132133Возвращает копию текущего объекта [`Context`](https://python-all.ru/3/library/contextvars.html#contextvars.Context).134135Следующий фрагмент получает копию текущего контекста и выводит все переменные и их значения, установленные в нём:136137```python138ctx: Context = copy_context()139print(list(ctx.items()))140```141142Функция имеет сложность *O*(1), т.е. работает одинаково быстро для контекстов с несколькими контекстными переменными и для контекстов, содержащих их много.143144#### `class contextvars.Context`145146Отображение [`ContextVars`](https://python-all.ru/3/library/contextvars.html#contextvars.ContextVar) на их значения.147148`Context()` создаёт пустой контекст без значений. Чтобы получить копию текущего контекста, используйте функцию [`copy_context()`](https://python-all.ru/3/library/contextvars.html#contextvars.copy_context).149150Каждый поток имеет собственный результирующий стек объектов `Context`. [Текущий контекст](https://python-all.ru/3/glossary.html#term-current-context) – это объект `Context` на вершине стека текущего потока. Все объекты `Context` в стеках считаются *вошедшими*.151152*Вход* в контекст, который можно выполнить вызовом его метода [`run()`](https://python-all.ru/3/library/contextvars.html#contextvars.Context.run), делает контекст текущим, помещая его на вершину стека контекстов текущего потока.153154*Выход* из текущего контекста, который можно выполнить возвратом из колбэка, переданного методу [`run()`](https://python-all.ru/3/library/contextvars.html#contextvars.Context.run), восстанавливает текущий контекст до состояния, в котором он был до входа, путём удаления контекста с вершины стека контекстов.155156Поскольку каждый поток имеет собственный стек контекстов, объекты [`ContextVar`](https://python-all.ru/3/library/contextvars.html#contextvars.ContextVar) ведут себя аналогично [`threading.local()`](https://python-all.ru/3/library/threading.html#threading.local) при присваивании значений в разных потоках.157158Попытка войти в уже вошедший контекст, включая контексты, вошедшие в других потоках, вызывает [`RuntimeError`](https://python-all.ru/3/library/exceptions.html#RuntimeError).159160После выхода из контекста его можно повторно войти (из любого потока).161162Любые изменения значений [`ContextVar`](https://python-all.ru/3/library/contextvars.html#contextvars.ContextVar) через метод [`ContextVar.set()`](https://python-all.ru/3/library/contextvars.html#contextvars.ContextVar.set) записываются в текущий контекст. Метод [`ContextVar.get()`](https://python-all.ru/3/library/contextvars.html#contextvars.ContextVar.get) возвращает значение, связанное с текущим контекстом. Выход из контекста эффективно отменяет любые изменения контекстных переменных, сделанные во время нахождения в контексте (при необходимости значения можно восстановить, повторно войдя в контекст).163164Контекст реализует интерфейс [`collections.abc.Mapping`](https://python-all.ru/3/library/collections.abc.html#collections.abc.Mapping).165166#### `run(callable, *args, **kwargs)`167168Входит в контекст, выполняет `callable(*args, **kwargs)`, затем выходит из контекста. Возвращает возвращаемое значение *вызываемого объекта* или распространяет исключение, если оно произошло.169170Пример:171172```python173import contextvars174175var = contextvars.ContextVar('var')176var.set('spam')177print(var.get()) # 'spam'178179ctx = contextvars.copy_context()180181def main():182 # 'var' было установлено в 'spam' до183 # вызова 'copy_context()' и 'ctx.run(main)', поэтому:184 print(var.get()) # 'spam'185 print(ctx[var]) # 'spam'186187 var.set('ham')188189 # Теперь, после установки 'var' в 'ham':190 print(var.get()) # 'ham'191 print(ctx[var]) # 'ham'192193# Любые изменения, которые функция 'main' вносит в 'var'194# будут содержаться в 'ctx'.195ctx.run(main)196197# Функция 'main()' была запущена в контексте 'ctx',198# поэтому изменения 'var' содержатся в нём:199print(ctx[var]) # 'ham'200201# Однако вне 'ctx' переменная 'var' по-прежнему равна 'spam':202print(var.get()) # 'spam'203```204205#### `copy()`206207Возвращает поверхностную копию объекта контекста.208209#### `var in context`210211Возвращает `True`, если в *контексте* установлено значение для *var*; в противном случае возвращает `False`.212213#### `context[var]`214215Возвращает значение переменной *var* [`ContextVar`](https://python-all.ru/3/library/contextvars.html#contextvars.ContextVar). Если переменная не установлена в объекте контекста, возбуждается [`KeyError`](https://python-all.ru/3/library/exceptions.html#KeyError).216217#### `get(var[, default])`218219Возвращает значение *var*, если *var* имеет значение в объекте контекста. В противном случае возвращает *default*. Если *default* не указан, возвращает `None`.220221#### `iter(context)`222223Возвращает итератор по переменным, хранящимся в объекте контекста.224225#### `len(proxy)`226227Возвращает количество переменных, установленных в объекте контекста.228229#### `keys()`230231Возвращает список всех переменных в объекте контекста.232233#### `values()`234235Возвращает список всех значений переменных в объекте контекста.236237#### `items()`238239Возвращает список кортежей из двух элементов, содержащих все переменные и их значения в объекте контекста.240241## Поддержка asyncio242243Контекстные переменные нативно поддерживаются в [`asyncio`](https://python-all.ru/3/library/asyncio.html#module-asyncio) и готовы к использованию без дополнительной настройки. Например, вот простой эхо-сервер, который использует контекстную переменную, чтобы сделать адрес удаленного клиента доступным в задаче, обрабатывающей этого клиента:244245```python246import asyncio247import contextvars248249client_addr_var = contextvars.ContextVar('client_addr')250251def render_goodbye():252 # Адрес текущего обрабатываемого клиента можно получить253 # не передавая его явно в эту функцию.254255 client_addr = client_addr_var.get()256 return f'Good bye, client @ {client_addr}\r\n'.encode()257258async def handle_request(reader, writer):259 addr = writer.transport.get_extra_info('socket').getpeername()260 client_addr_var.set(addr)261262 # В любом вызываемом нами коде теперь можно получить263 # адрес клиента, вызвав 'client_addr_var.get()'.264265 while True:266 line = await reader.readline()267 print(line)268 if not line.strip():269 break270271 writer.write(b'HTTP/1.1 200 OK\r\n') # строка состояния272 writer.write(b'\r\n') # заголовки273 writer.write(render_goodbye()) # тело274 writer.close()275276async def main():277 srv = await asyncio.start_server(278 handle_request, '127.0.0.1', 8081)279280 async with srv:281 await srv.serve_forever()282283asyncio.run(main())284285# Для тестирования можно использовать telnet или curl:286# telnet 127.0.0.1 8081287# curl 127.0.0.1:8081288```289