Context Variables
The `contextvars` library is a backport of the standard library `contextvars` module (introduced in Python 3.7 via PEP 567), providing APIs to manage, store, and access context-local state. It enables task-local variables in asynchronous code, ensuring data isolation across different coroutines or threads without explicit argument passing. The current version is 2.4, and it typically releases on demand for bug fixes or dependency updates.
Warnings
- gotcha Creating `ContextVar` instances within closures or functions repeatedly (e.g., inside a loop or request handler) can lead to memory leaks in long-running applications. Each call creates a new `ContextVar` object that may persist in the context graph.
- gotcha Calling `ContextVar.reset(token)` with a token that was created in a different `Context` will raise a `ValueError`. Additionally, a `RuntimeError` is raised if a token is used to reset a variable more than once.
- gotcha Forgetting to `reset()` a `ContextVar` after `set()` in long-running applications (e.g., web servers, background workers) can lead to state bleeding, where subsequent tasks inherit an unexpected context value. This is a common source of hard-to-debug issues.
- gotcha Attempting to get the value of a `ContextVar` using `ContextVar.get()` when no value has been set in the current context and no `default` value was provided during `ContextVar` instantiation will raise a `LookupError`.
- gotcha Contexts are not automatically propagated across process boundaries. If `contextvars` are used with `multiprocessing.ProcessPoolExecutor`, changes made in a worker process's context will not affect the main process's context or other worker processes, as context variables are copied (pickled) when tasks are assigned.
Install
-
pip install contextvars
Imports
- ContextVar
from contextvars import ContextVar
- copy_context
from contextvars import copy_context
- current_context
from contextvars import current_context
- Token
from contextvars import Token
Quickstart
import asyncio
import contextvars
# Define a context variable
current_user = contextvars.ContextVar('current_user', default='Guest')
async def handle_request(user_name):
# Set the user for the current task. This returns a Token.
token = current_user.set(user_name)
try:
print(f"Task for {user_name}: Currently handled by {current_user.get()}")
await asyncio.sleep(0.1) # Simulate async work
print(f"Task for {user_name}: Still handled by {current_user.get()}")
finally:
# Reset the context variable to its previous value using the token
current_user.reset(token)
async def main():
print(f"Before tasks: {current_user.get()}")
await asyncio.gather(
handle_request("Alice"),
handle_request("Bob")
)
print(f"After tasks: {current_user.get()}") # Should revert to default
if __name__ == '__main__':
asyncio.run(main())