summaryrefslogtreecommitdiffstats
path: root/Doc/library
diff options
context:
space:
mode:
authorRichard Hansen <rhansen@rhansen.org>2024-10-09 23:44:03 (GMT)
committerGitHub <noreply@github.com>2024-10-09 23:44:03 (GMT)
commit99400930ac1d4e5e10a5ae30f8202d8bc2661e39 (patch)
treec10248b89e10f17d1ea801755e62ce469b41d5a5 /Doc/library
parent942916378aa6a0946b1385c2c7ca6935620d710a (diff)
downloadcpython-99400930ac1d4e5e10a5ae30f8202d8bc2661e39.zip
cpython-99400930ac1d4e5e10a5ae30f8202d8bc2661e39.tar.gz
cpython-99400930ac1d4e5e10a5ae30f8202d8bc2661e39.tar.bz2
gh-124872: Refine contextvars documentation (#124773)
* Add definitions for "context", "current context", and "context management protocol". * Update related definitions to be consistent with the new definitions. * Restructure the documentation for the `contextvars.Context` class to prepare for adding context manager support, and for consistency with the definitions. * Use `testcode` and `testoutput` to test the `Context.run` example. * Expand the documentation for the `Py_CONTEXT_EVENT_ENTER` and `Py_CONTEXT_EVENT_EXIT` events to clarify and to prepare for planned changes.
Diffstat (limited to 'Doc/library')
-rw-r--r--Doc/library/contextvars.rst98
1 files changed, 68 insertions, 30 deletions
diff --git a/Doc/library/contextvars.rst b/Doc/library/contextvars.rst
index 2a79dfe..2b1fb9f 100644
--- a/Doc/library/contextvars.rst
+++ b/Doc/library/contextvars.rst
@@ -144,51 +144,89 @@ Manual Context Management
To get a copy of the current context use the
:func:`~contextvars.copy_context` function.
- Every thread will have a different top-level :class:`~contextvars.Context`
- object. This means that a :class:`ContextVar` object behaves in a similar
- fashion to :func:`threading.local` when values are assigned in different
- threads.
+ Each thread has its own effective stack of :class:`!Context` objects. The
+ :term:`current context` is the :class:`!Context` object at the top of the
+ current thread's stack. All :class:`!Context` objects in the stacks are
+ considered to be *entered*.
+
+ *Entering* a context, which can be done by calling its :meth:`~Context.run`
+ method, makes the context the current context by pushing it onto the top of
+ the current thread's context stack.
+
+ *Exiting* from the current context, which can be done by returning from the
+ callback passed to the :meth:`~Context.run` method, restores the current
+ context to what it was before the context was entered by popping the context
+ off the top of the context stack.
+
+ Since each thread has its own context stack, :class:`ContextVar` objects
+ behave in a similar fashion to :func:`threading.local` when values are
+ assigned in different threads.
+
+ Attempting to enter an already entered context, including contexts entered in
+ other threads, raises a :exc:`RuntimeError`.
+
+ After exiting a context, it can later be re-entered (from any thread).
+
+ Any changes to :class:`ContextVar` values via the :meth:`ContextVar.set`
+ method are recorded in the current context. The :meth:`ContextVar.get`
+ method returns the value associated with the current context. Exiting a
+ context effectively reverts any changes made to context variables while the
+ context was entered (if needed, the values can be restored by re-entering the
+ context).
Context implements the :class:`collections.abc.Mapping` interface.
.. method:: run(callable, *args, **kwargs)
- Execute ``callable(*args, **kwargs)`` code in the context object
- the *run* method is called on. Return the result of the execution
- or propagate an exception if one occurred.
+ Enters the Context, executes ``callable(*args, **kwargs)``, then exits the
+ Context. Returns *callable*'s return value, or propagates an exception if
+ one occurred.
+
+ Example:
+
+ .. testcode::
+
+ import contextvars
- Any changes to any context variables that *callable* makes will
- be contained in the context object::
+ var = contextvars.ContextVar('var')
+ var.set('spam')
+ print(var.get()) # 'spam'
- var = ContextVar('var')
- var.set('spam')
+ ctx = contextvars.copy_context()
- def main():
- # 'var' was set to 'spam' before
- # calling 'copy_context()' and 'ctx.run(main)', so:
- # var.get() == ctx[var] == 'spam'
+ def main():
+ # 'var' was set to 'spam' before
+ # calling 'copy_context()' and 'ctx.run(main)', so:
+ print(var.get()) # 'spam'
+ print(ctx[var]) # 'spam'
- var.set('ham')
+ var.set('ham')
- # Now, after setting 'var' to 'ham':
- # var.get() == ctx[var] == 'ham'
+ # Now, after setting 'var' to 'ham':
+ print(var.get()) # 'ham'
+ print(ctx[var]) # 'ham'
- ctx = copy_context()
+ # Any changes that the 'main' function makes to 'var'
+ # will be contained in 'ctx'.
+ ctx.run(main)
- # Any changes that the 'main' function makes to 'var'
- # will be contained in 'ctx'.
- ctx.run(main)
+ # The 'main()' function was run in the 'ctx' context,
+ # so changes to 'var' are contained in it:
+ print(ctx[var]) # 'ham'
- # The 'main()' function was run in the 'ctx' context,
- # so changes to 'var' are contained in it:
- # ctx[var] == 'ham'
+ # However, outside of 'ctx', 'var' is still set to 'spam':
+ print(var.get()) # 'spam'
- # However, outside of 'ctx', 'var' is still set to 'spam':
- # var.get() == 'spam'
+ .. testoutput::
+ :hide:
- The method raises a :exc:`RuntimeError` when called on the same
- context object from more than one OS thread, or when called
- recursively.
+ spam
+ spam
+ spam
+ ham
+ ham
+ ham
+ spam
.. method:: copy()