contextvars – Контекстные переменные
Этот модуль предоставляет API для управления, хранения и доступа к контекстно-локальному состоянию. Класс ContextVar используется для объявления и работы с контекстными переменными. Функция copy_context() и класс Context должны использоваться для управления текущим контекстом в асинхронных фреймворках.
Менеджеры контекста, имеющие состояние, должны использовать контекстные переменные вместо threading.local(), чтобы предотвратить случайное распространение своего состояния на другой код при использовании в конкурентном коде.
См. также PEP 567 для получения дополнительных сведений.
Добавлено в версии 3.7.
Context VariablesКонтекстные переменные
- class contextvars.ContextVar(name[, *, default])¶
Этот класс используется для объявления новой контекстной переменной, например:
pythonvar: ContextVar[int] = ContextVar('var', default=42)Обязательный параметр name используется для интроспекции и отладки.
Необязательный именованный параметр default возвращается
ContextVar.get(), когда для переменной не найдено значение в текущем контексте.Важно: Контекстные переменные должны создаваться на верхнем уровне модуля и никогда в замыканиях. Объекты
Contextсодержат сильные ссылки на контекстные переменные, что препятствует корректной сборке мусора контекстных переменных.- name¶
Имя переменной. Это свойство только для чтения.
Добавлено в версии 3.7.1.
- get([default])¶
Возвращает значение для контекстной переменной для текущего контекста.
Если для переменной нет значения в текущем контексте, метод:
возвращает значение аргумента default метода, если он указан; или
возвращает значение по умолчанию для контекстной переменной, если она была создана с ним; или
вызывает
LookupError.
- set(value)¶
Устанавливает новое значение для контекстной переменной в текущем контексте.
Обязательный аргумент value – это новое значение для контекстной переменной.
Возвращает объект
Token, который можно использовать для восстановления предыдущего значения переменной с помощью методаContextVar.reset().Для удобства объект токена можно использовать в качестве менеджера контекста, чтобы избежать ручного вызова
ContextVar.reset():pythonvar = ContextVar('var', default='default value') with var.set('new value'): assert var.get() == 'new value' assert var.get() == 'default value'Это сокращение для:
pythonvar = ContextVar('var', default='default value') token = var.set('new value') try: assert var.get() == 'new value' finally: var.reset(token) assert var.get() == 'default value'Добавлено в версии 3.14: Добавлена поддержка использования токенов в качестве менеджеров контекста.
- reset(token)¶
Сбрасывает контекстную переменную до значения, которое она имела до использования
ContextVar.set(), создавшего токен.Например:
pythonvar = ContextVar('var') token = var.set('new value') # код, использующий 'var'; var.get() возвращает 'new value'. var.reset(token) # После вызова reset var снова не имеет значения, # var.get() вызовет LookupError.Один и тот же токен нельзя использовать дважды.
- class contextvars.Token¶
Токен объекты возвращаются методом
ContextVar.set(). Они могут быть переданы методуContextVar.reset()для возврата значения переменной к тому, каким оно было до соответствующего set. Один токен не может сбросить контекстную переменную более одного раза.Токены поддерживают протокол менеджера контекста для автоматического сброса контекстных переменных. См.
ContextVar.set().Добавлено в версии 3.14: Добавлена поддержка использования в качестве менеджера контекста.
- var¶
Свойство только для чтения. Указывает на объект
ContextVar, создавший токен.
- old_value¶
Свойство только для чтения. Установлено в значение, которое переменная имела до вызова метода
ContextVar.set(), создавшего токен. Указывает наToken.MISSING, если переменная не была установлена до вызова.
- MISSING¶
Объект-маркер, используемый
Token.old_value.
Manual Context ManagementУправление контекстом вручную
- contextvars.copy_context()¶
Возвращает копию текущего объекта
Context.Следующий фрагмент получает копию текущего контекста и выводит все переменные и их значения, которые в нем установлены:
pythonctx: Context = copy_context() print(list(ctx.items()))Функция имеет сложность O(1), т.е. работает одинаково быстро для контекстов с несколькими контекстными переменными и для контекстов, которые имеют много таких переменных.
- class contextvars.Context¶
Отображение
ContextVarsна их значения.Context()создаёт пустой контекст без значений в нём. Чтобы получить копию текущего контекста, используйте функциюcopy_context().Каждый поток имеет свой эффективный стек объектов
Context. Текущий контекст – это объектContextна вершине стека текущего потока. Все объектыContextв стеках считаются вошедшими.Вход в контекст, который может быть выполнен вызовом его метода
run(), делает контекст текущим, помещая его на вершину стека контекстов текущего потока.Выход из текущего контекста, который может быть выполнен возвратом из колбэка, переданного методу
run(), восстанавливает текущий контекст до состояния, в котором он был до входа в контекст, путём извлечения контекста с вершины стека контекстов.Поскольку каждый поток имеет свой стек контекстов, объекты
ContextVarведут себя аналогичноthreading.local()при присваивании значений в разных потоках.Попытка войти в уже вошедший контекст, включая контексты, вошедшие в других потоках, вызывает
RuntimeError.После выхода из контекста его можно повторно войти (из любого потока).
Любые изменения значений
ContextVarчерез методContextVar.set()записываются в текущий контекст. МетодContextVar.get()возвращает значение, связанное с текущим контекстом. Выход из контекста эффективно отменяет любые изменения, внесённые в контекстные переменные, пока контекст был введён (при необходимости значения можно восстановить, повторно войдя в контекст).Context реализует интерфейс
collections.abc.Mapping.- run(callable, *args, **kwargs)¶
Входит в контекст, выполняет
callable(*args, **kwargs), затем выходит из контекста. Возвращает возвращаемое значение вызываемого объекта или распространяет исключение, если оно произошло.Пример:
pythonimport contextvars var = contextvars.ContextVar('var') var.set('spam') print(var.get()) # 'spam' ctx = contextvars.copy_context() def main(): # ранее 'var' был установлен в 'spam' # вызов 'copy_context()' и 'ctx.run(main)', поэтому: print(var.get()) # 'spam' print(ctx[var]) # 'spam' var.set('ham') # Теперь, после установки 'var' в 'ham': print(var.get()) # 'ham' print(ctx[var]) # 'ham' # Любые изменения, которые функция 'main' вносит в 'var' # будут содержаться в 'ctx'. ctx.run(main) # Функция 'main()' была запущена в контексте 'ctx', # поэтому изменения 'var' содержатся в нем: print(ctx[var]) # 'ham' # Однако вне 'ctx' 'var' все еще установлен в 'spam': print(var.get()) # 'spam'
- copy()¶
Возвращает поверхностную копию объекта контекста.
- var in context
Возвращает
True, если context имеет установленное значение для var; в противном случае возвращаетFalse.
- context[var]
Возвращает значение переменной var
ContextVar. Если переменная не установлена в объекте контекста, возбуждаетсяKeyError.
- get(var[, default])¶
Возвращает значение для var, если var имеет значение в объекте контекста. В противном случае возвращает default. Если default не указан, возвращает
None.
- iter(context)
Возвращает итератор по переменным, хранящимся в объекте контекста.
- len(proxy)
Возвращает количество переменных, установленных в объекте контекста.
- keys()¶
Возвращает список всех переменных в объекте контекста.
- values()¶
Возвращает список значений всех переменных в объекте контекста.
- items()¶
Возвращает список 2-кортежей, содержащих все переменные и их значения в объекте контекста.
asyncio supportПоддержка asyncio
Переменные контекста изначально поддерживаются в asyncio и готовы к использованию без дополнительной настройки. Например, вот простой эхо-сервер, который использует переменную контекста, чтобы сделать адрес удаленного клиента доступным в задаче, обрабатывающей этого клиента:
import asyncio
import contextvars
client_addr_var = contextvars.ContextVar('client_addr')
def render_goodbye():
# Адрес текущего обрабатываемого клиента можно получить
# без явной передачи его этой функции.
client_addr = client_addr_var.get()
return f'Good bye, client @ {client_addr}\r\n'.encode()
async def handle_request(reader, writer):
addr = writer.transport.get_extra_info('socket').getpeername()
client_addr_var.set(addr)
# В любом вызываемом нами коде теперь можно получить
# адрес клиента, вызвав 'client_addr_var.get()'.
while True:
line = await reader.readline()
print(line)
if not line.strip():
break
writer.write(b'HTTP/1.1 200 OK\r\n') # строка состояния
writer.write(b'\r\n') # заголовки
writer.write(render_goodbye()) # тело
writer.close()
async def main():
srv = await asyncio.start_server(
handle_request, '127.0.0.1', 8081)
async with srv:
await srv.serve_forever()
asyncio.run(main())
# Чтобы протестировать это, можно использовать telnet или curl:
# telnet 127.0.0.1 8081
# curl 127.0.0.1:8081