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.
This commit is contained in:
Richard Hansen 2024-10-09 19:44:03 -04:00 committed by GitHub
parent 942916378a
commit 99400930ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 132 additions and 52 deletions

View File

@ -122,18 +122,24 @@ Context object management functions:
.. c:type:: PyContextEvent .. c:type:: PyContextEvent
Enumeration of possible context object watcher events: Enumeration of possible context object watcher events:
- ``Py_CONTEXT_EVENT_ENTER``
- ``Py_CONTEXT_EVENT_EXIT`` - ``Py_CONTEXT_EVENT_ENTER``: A context has been entered, causing the
:term:`current context` to switch to it. The object passed to the watch
callback is the now-current :class:`contextvars.Context` object. Each
enter event will eventually have a corresponding exit event for the same
context object after any subsequently entered contexts have themselves been
exited.
- ``Py_CONTEXT_EVENT_EXIT``: A context is about to be exited, which will
cause the :term:`current context` to switch back to what it was before the
context was entered. The object passed to the watch callback is the
still-current :class:`contextvars.Context` object.
.. versionadded:: 3.14 .. versionadded:: 3.14
.. c:type:: int (*PyContext_WatchCallback)(PyContextEvent event, PyContext* ctx) .. c:type:: int (*PyContext_WatchCallback)(PyContextEvent event, PyContext* ctx)
Type of a context object watcher callback function. Context object watcher callback function. The object passed to the callback
If *event* is ``Py_CONTEXT_EVENT_ENTER``, then the callback is invoked is event-specific; see :c:type:`PyContextEvent` for details.
after *ctx* has been set as the current context for the current thread.
Otherwise, the callback is invoked before the deactivation of *ctx* as the current context
and the restoration of the previous contex object for the current thread.
If the callback returns with an exception set, it must return ``-1``; this If the callback returns with an exception set, it must return ``-1``; this
exception will be printed as an unraisable exception using exception will be printed as an unraisable exception using

View File

@ -265,19 +265,33 @@ Glossary
advanced mathematical feature. If you're not aware of a need for them, advanced mathematical feature. If you're not aware of a need for them,
it's almost certain you can safely ignore them. it's almost certain you can safely ignore them.
context
This term has different meanings depending on where and how it is used.
Some common meanings:
* The temporary state or environment established by a :term:`context
manager` via a :keyword:`with` statement.
* The collection of key­value bindings associated with a particular
:class:`contextvars.Context` object and accessed via
:class:`~contextvars.ContextVar` objects. Also see :term:`context
variable`.
* A :class:`contextvars.Context` object. Also see :term:`current
context`.
context management protocol
The :meth:`~object.__enter__` and :meth:`~object.__exit__` methods called
by the :keyword:`with` statement. See :pep:`343`.
context manager context manager
An object which controls the environment seen in a :keyword:`with` An object which implements the :term:`context management protocol` and
statement by defining :meth:`~object.__enter__` and :meth:`~object.__exit__` methods. controls the environment seen in a :keyword:`with` statement. See
See :pep:`343`. :pep:`343`.
context variable context variable
A variable which can have different values depending on its context. A variable whose value depends on which context is the :term:`current
This is similar to Thread-Local Storage in which each execution context`. Values are accessed via :class:`contextvars.ContextVar`
thread may have a different value for a variable. However, with context objects. Context variables are primarily used to isolate state between
variables, there may be several contexts in one execution thread and the
main usage for context variables is to keep track of variables in
concurrent asynchronous tasks. concurrent asynchronous tasks.
See :mod:`contextvars`.
contiguous contiguous
.. index:: C-contiguous, Fortran contiguous .. index:: C-contiguous, Fortran contiguous
@ -311,6 +325,14 @@ Glossary
is used when necessary to distinguish this implementation from others is used when necessary to distinguish this implementation from others
such as Jython or IronPython. such as Jython or IronPython.
current context
The :term:`context` (:class:`contextvars.Context` object) that is
currently used by :class:`~contextvars.ContextVar` objects to access (get
or set) the values of :term:`context variables <context variable>`. Each
thread has its own current context. Frameworks for executing asynchronous
tasks (see :mod:`asyncio`) associate each task with a context which
becomes the current context whenever the task starts or resumes execution.
decorator decorator
A function returning another function, usually applied as a function A function returning another function, usually applied as a function
transformation using the ``@wrapper`` syntax. Common examples for transformation using the ``@wrapper`` syntax. Common examples for

View File

@ -144,51 +144,89 @@ Manual Context Management
To get a copy of the current context use the To get a copy of the current context use the
:func:`~contextvars.copy_context` function. :func:`~contextvars.copy_context` function.
Every thread will have a different top-level :class:`~contextvars.Context` Each thread has its own effective stack of :class:`!Context` objects. The
object. This means that a :class:`ContextVar` object behaves in a similar :term:`current context` is the :class:`!Context` object at the top of the
fashion to :func:`threading.local` when values are assigned in different current thread's stack. All :class:`!Context` objects in the stacks are
threads. 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. Context implements the :class:`collections.abc.Mapping` interface.
.. method:: run(callable, *args, **kwargs) .. method:: run(callable, *args, **kwargs)
Execute ``callable(*args, **kwargs)`` code in the context object Enters the Context, executes ``callable(*args, **kwargs)``, then exits the
the *run* method is called on. Return the result of the execution Context. Returns *callable*'s return value, or propagates an exception if
or propagate an exception if one occurred. one occurred.
Any changes to any context variables that *callable* makes will Example:
be contained in the context object::
var = ContextVar('var') .. testcode::
var.set('spam')
def main(): import contextvars
# 'var' was set to 'spam' before
# calling 'copy_context()' and 'ctx.run(main)', so:
# var.get() == ctx[var] == 'spam'
var.set('ham') var = contextvars.ContextVar('var')
var.set('spam')
print(var.get()) # 'spam'
# Now, after setting 'var' to 'ham': ctx = contextvars.copy_context()
# var.get() == ctx[var] == 'ham'
ctx = copy_context() def main():
# 'var' was set to 'spam' before
# calling 'copy_context()' and 'ctx.run(main)', so:
print(var.get()) # 'spam'
print(ctx[var]) # 'spam'
# Any changes that the 'main' function makes to 'var' var.set('ham')
# will be contained in 'ctx'.
ctx.run(main)
# The 'main()' function was run in the 'ctx' context, # Now, after setting 'var' to 'ham':
# so changes to 'var' are contained in it: print(var.get()) # 'ham'
# ctx[var] == 'ham' print(ctx[var]) # 'ham'
# However, outside of 'ctx', 'var' is still set to 'spam': # Any changes that the 'main' function makes to 'var'
# var.get() == 'spam' # will be contained in 'ctx'.
ctx.run(main)
The method raises a :exc:`RuntimeError` when called on the same # The 'main()' function was run in the 'ctx' context,
context object from more than one OS thread, or when called # so changes to 'var' are contained in it:
recursively. print(ctx[var]) # 'ham'
# However, outside of 'ctx', 'var' is still set to 'spam':
print(var.get()) # 'spam'
.. testoutput::
:hide:
spam
spam
spam
ham
ham
ham
spam
.. method:: copy() .. method:: copy()

View File

@ -28,15 +28,26 @@ PyAPI_FUNC(int) PyContext_Enter(PyObject *);
PyAPI_FUNC(int) PyContext_Exit(PyObject *); PyAPI_FUNC(int) PyContext_Exit(PyObject *);
typedef enum { typedef enum {
Py_CONTEXT_EVENT_ENTER, /*
Py_CONTEXT_EVENT_EXIT, * A context has been entered, causing the "current context" to switch to
* it. The object passed to the watch callback is the now-current
* contextvars.Context object. Each enter event will eventually have a
* corresponding exit event for the same context object after any
* subsequently entered contexts have themselves been exited.
*/
Py_CONTEXT_EVENT_ENTER,
/*
* A context is about to be exited, which will cause the "current context"
* to switch back to what it was before the context was entered. The
* object passed to the watch callback is the still-current
* contextvars.Context object.
*/
Py_CONTEXT_EVENT_EXIT,
} PyContextEvent; } PyContextEvent;
/* /*
* Callback to be invoked when a context object is entered or exited. * Context object watcher callback function. The object passed to the callback
* * is event-specific; see PyContextEvent for details.
* The callback is invoked with the event and a reference to
* the context after its entered and before its exited.
* *
* if the callback returns with an exception set, it must return -1. Otherwise * if the callback returns with an exception set, it must return -1. Otherwise
* it should return 0 * it should return 0

View File

@ -0,0 +1,3 @@
Added definitions for :term:`context`, :term:`current context`, and
:term:`context management protocol`, updated related definitions to be
consistent, and expanded the documentation for :class:`contextvars.Context`.