<div dir="ltr"><div class="gmail_default" style="font-family:monospace,monospace">Hi all,</div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace">Thank you for the feedback so far. FYI, or as a reminder, this is now PEP 555, but the web version is still the same draft that I posted here.</div><div class="gmail_extra"><br></div><div class="gmail_extra"><div class="gmail_default" style="font-family:monospace,monospace">​The discussion of this was paused as there was a lot going on at that moment, but I'm now getting ready to make a next version of the draft.​ Below, I'll draft some changes I intend to make so they can already be discussed.</div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace">First of all, I'm considering calling the concept "context arguments" instead of "context variables", because that describes the concept better. But see below for some more.</div></div><div class="gmail_extra"><br><div class="gmail_quote">On Tue, Sep 5, 2017 at 12:50 AM, Koos Zevenhoven <span dir="ltr"><<a href="mailto:k7hoven@gmail.com" target="_blank">k7hoven@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;padding-left:1ex;border-left-color:rgb(204,204,204);border-left-width:1px;border-left-style:solid"><div dir="ltr"><div><div><font face="monospace, monospace">Hi all,</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">as promised, here is a draft PEP for context variable semantics and implementation. Apologies for the slight delay; I had a not-so-minor autosave accident and had to retype the majority of this first draft.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">During the past years, there has been growing interest in something like task-local storage or async-local storage. This PEP proposes an alternative approach to solving the problems that are typically stated as motivation for such concepts.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">This proposal is based on sketches of solutions since spring 2015, with some minor influences from the recent discussion related to PEP 550. I can also see some potential implementation synergy between this PEP and PEP 550, even if the proposed semantics are quite different.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">So, here it is. This is the first draft and some things are still missing, but the essential things should be there.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">-- Koos</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">||||||||||||||||||||||||||||||<wbr>||||||||||||||||||||||||||||</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">PEP: 999</font></div><div><font face="monospace, monospace">Title: Context-local variables (contextvars)</font></div><div><font face="monospace, monospace">Version: $Revision$</font></div><div><font face="monospace, monospace">Last-Modified: $Date$</font></div><div><font face="monospace, monospace">Author: Koos Zevenhoven</font></div><div><font face="monospace, monospace">Status: Draft</font></div><div><font face="monospace, monospace">Type: Standards Track</font></div><div><font face="monospace, monospace">Content-Type: text/x-rst</font></div><div><font face="monospace, monospace">Created: DD-Mmm-YYYY</font></div><div><font face="monospace, monospace">Post-History: DD-Mmm-YYYY</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Abstract</font></div><div><font face="monospace, monospace">========</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Sometimes, in special cases, it is desired that code can pass information down the function call chain to the callees without having to explicitly pass the information as arguments to each function in the call chain. This proposal describes a construct which allows code to explicitly switch in and out of a context where a certain context variable has a given value assigned to it. This is a modern alternative to some uses of things like global variables in traditional single-threaded (or thread-unsafe) code and of thread-local storage in traditional *concurrency-unsafe* code (single- or multi-threaded). In particular, the proposed mechanism can also be used with more modern concurrent execution mechanisms such as asynchronously executed coroutines, without the concurrently executed call chains interfering with each other's contexts. </font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">The "call chain" can consist of normal functions, awaited coroutines, or generators. The semantics of context variable scope are equivalent in all cases, allowing code to be refactored freely into *subroutines* (which here refers to functions, sub-generators or sub-coroutines) without affecting the semantics of context variables. Regarding implementation, this proposal aims at simplicity and minimum changes to the CPython interpreter and to other Python interpreters.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Rationale</font></div><div><font face="monospace, monospace">=========</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Consider a modern Python *call chain* (or call tree), which in this proposal refers to any chained (nested) execution of *subroutines*, using any possible combinations of normal function calls, or expressions using ``await`` or ``yield from``. In some cases, passing necessary *information* down the call chain as arguments can substantially complicate the required function signatures, or it can even be impossible to achieve in practice. In these cases, one may search for another place to store this information. Let us look at some historical examples. </font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">The most naive option is to assign the value to a global variable or similar, where the code down the call chain can access it. However, this immediately makes the code thread-unsafe, because with multiple threads, all threads assign to the same global variable, and another thread can interfere at any point in the call chain.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">A somewhat less naive option is to store the information as per-thread information in thread-local storage, where each thread has its own "copy" of the variable which other threads cannot interfere with. Although non-ideal, this has been the best solution in many cases. However, thanks to generators and coroutines, the execution of the call chain can be suspended and resumed, allowing code in other contexts to run concurrently. Therefore, using thread-local storage is *concurrency-unsafe*, because other call chains in other contexts may interfere with the thread-local variable.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Note that in the above two historical approaches, the stored information has the *widest* available scope without causing problems. For a third solution along the same path, one would first define an equivalent of a "thread" for asynchronous execution and concurrency. This could be seen as the largest amount of code and nested calls that is guaranteed to be executed sequentially without ambiguity in execution order. This might be referred to as concurrency-local or task-local storage. In this meaning of "task", there is no ambiguity in the order of execution of the code within one task. (This concept of a task is close to equivalent to a ``Task`` in ``asyncio``, but not exactly.) In such concurrency-locals, it is possible to pass information down the call chain to callees without another code path interfering with the value in the background.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Common to the above approaches is that they indeed use variables with a wide but just-narrow-enough scope. Thread-locals could also be called thread-wide globals---in single-threaded code, they are indeed truly global. And task-locals could be called task-wide globals, because tasks can be very big. </font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">The issue here is that neither global variables, thread-locals nor task-locals are really meant to be used for this purpose of passing information of the execution context down the call chain. Instead of the widest possible variable scope, the scope of the variables should be controlled by the programmer, typically of a library, to have the desired scope---not wider. In other words, task-local variables (and globals and thread-locals) have nothing to do with the kind of context-bound information passing that this proposal intends to enable, even if task-locals can be used to emulate the desired semantics. Therefore, in the following, this proposal describes the semantics and the outlines of an implementation for *context-local variables* (or context variables, contextvars). In fact, as a side effect of this PEP, an async framework can use the proposed feature to implement task-local variables.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Proposal</font></div><div><font face="monospace, monospace">========</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Because the proposed semantics are not a direct extension to anything already available in Python, this proposal is first described in terms of semantics and API at a fairly high level. In particular, Python ``with`` statements are heavily used in the description, as they are a good match with the proposed semantics. However, the underlying ``__enter__`` and ``__exit__`` methods correspond to functions in the lower-level speed-optimized (C) API. For clarity of this document, the lower-level functions are not explicitly named in the definition of the semantics. After describing the semantics and high-level API, the implementation is described, going to a lower level.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Semantics and higher-level API</font></div><div><font face="monospace, monospace">------------------------------</font></div><div><font face="monospace, monospace"> </font></div><div><font face="monospace, monospace">Core concept</font></div><div><font face="monospace, monospace">''''''''''''</font></div><div><font face="monospace, monospace"> </font></div><div><font face="monospace, monospace">A context-local variable is represented by a single instance of ``contextvars.Var``, say ``cvar``. Any code that has access to the ``cvar`` object can ask for its value with respect to the current context. In the high-level API, this value is given by the ``cvar.value`` property::</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">    cvar = contextvars.Var(default="the default value", </font></div><div><font face="monospace, monospace">                           description="example context variable")</font></div><div><font face="monospace, monospace">    </font></div></div></div></blockquote><div><br></div><div><div class="gmail_default" style="font-family:monospace,monospace">​Some points related to the arguments and naming:​</div></div><div><br></div><div><div class="gmail_default" style="font-family:monospace,monospace">Indeed, this might change to contextvars.Arg. After all, these are more like arguments than variables. </div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace">But just like with function arguments, you can use a mutable value, which allows more variable-like semantics. That is, however, not the primarily intended use. It may also cause more problems at inter-process or inter-interpreter boundaries etc., where direct mutation of objects may not be possible.​</div><br></div><div><div class="gmail_default" style="font-family:monospace,monospace">​I might have to remove the ``default`` argument, at least in this form. If there is a default, it should be more explicit what the scope of the default is. There could be thread-wide defaults or interpreter-wide defaults and so on.​ It is not completely clear what a truly global default would mean. </div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace">One way to deal with this would be to always pass the context on to other threads and processes etc when they are created. But there are some ambiguities here too, so the safest way might be to let the user implement the desired semantics regarding defaults and thread boundaries etc.</div></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;padding-left:1ex;border-left-color:rgb(204,204,204);border-left-width:1px;border-left-style:solid"><div dir="ltr"><div><div><font face="monospace, monospace">    assert cvar.value == "the default value"  # default still applies</font></div><div><font face="monospace, monospace">    </font></div><div><font face="monospace, monospace">    # In code examples, all ``assert`` statements should</font></div><div><font face="monospace, monospace">    # succeed according to the proposed semantics. </font></div><div><font face="monospace, monospace">    </font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">No assignments to ``cvar`` have been applied for this context, so ``cvar.value`` gives the default value. Assigning new values to contextvars is done in a highly scope-aware manner::</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">    with cvar.assign(new_value):</font></div><div><font face="monospace, monospace">        assert cvar.value is new_value</font></div><div><font face="monospace, monospace">        # Any code here, or down the call chain from here, sees:</font></div><div><font face="monospace, monospace">        #     cvar.value is new_value</font></div><div><font face="monospace, monospace">        # unless another value has been assigned in a </font></div><div><font face="monospace, monospace">        # nested context</font></div><div><font face="monospace, monospace">        assert cvar.value is new_value</font></div><div><font face="monospace, monospace">    # the assignment of ``cvar`` to ``new_value`` is no longer visible </font></div><div><font face="monospace, monospace">    assert cvar.value == "the default value"</font></div><div><font face="monospace, monospace">    </font></div><div><font face="monospace, monospace">    </font></div><div><font face="monospace, monospace">Here, ``cvar.assign(value)`` returns another object, namely ``contextvars.Assignment(cvar, new_value)``. The essential part here is that applying a context variable assignment (``Assignment.__enter__``) is paired with a de-assignment (``Assignment.__exit__``). These operations set the bounds for the scope of the assigned value.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Assignments to the same context variable can be nested to override the outer assignment in a narrower context::</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">    assert cvar.value == "the default value"</font></div><div><font face="monospace, monospace">    with cvar.assign("outer"):</font></div><div><font face="monospace, monospace">        assert cvar.value == "outer"</font></div><div><font face="monospace, monospace">        with cvar.assign("inner"):</font></div><div><font face="monospace, monospace">            assert cvar.value == "inner"</font></div><div><font face="monospace, monospace">        assert cvar.value == "outer"</font></div><div><font face="monospace, monospace">    assert cvar.value == "the default value"</font></div><div><font face="monospace, monospace">    </font></div><div><font face="monospace, monospace">    </font></div><div><font face="monospace, monospace">Also multiple variables can be assigned to in a nested manner without affecting each other::</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">    cvar1 = contextvars.Var() </font></div><div><font face="monospace, monospace">    cvar2 = contextvars.Var() </font></div><div><font face="monospace, monospace">    </font></div><div><font face="monospace, monospace">    assert cvar1.value is None # default is None by default</font></div><div><font face="monospace, monospace">    assert cvar2.value is None</font></div><div><font face="monospace, monospace">    </font></div><div><font face="monospace, monospace">    with cvar1.assign(value1):</font></div><div><font face="monospace, monospace">        assert cvar1.value is value1</font></div><div><font face="monospace, monospace">        assert cvar2.value is None</font></div><div><font face="monospace, monospace">        with cvar2.assign(value2):</font></div><div><font face="monospace, monospace">            assert cvar1.value is value1</font></div><div><font face="monospace, monospace">            assert cvar2.value is value2</font></div><div><font face="monospace, monospace">        assert cvar1.value is value1</font></div><div><font face="monospace, monospace">        assert cvar2.value is None</font></div><div><font face="monospace, monospace">    assert cvar1.value is None</font></div><div><font face="monospace, monospace">    assert cvar2.value is None</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Or with more convenient Python syntax:: </font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">    with cvar1.assign(value1), cvar2.assign(value2):</font></div><div><font face="monospace, monospace">        assert cvar1.value is value1</font></div><div><font face="monospace, monospace">        assert cvar2.value is value2</font></div><div><font face="monospace, monospace">        </font></div><div><font face="monospace, monospace">In another *context*, in another thread or otherwise concurrently executed task or code path, the context variables can have a completely different state. The programmer thus only needs to worry about the context at hand.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Refactoring into subroutines</font></div><div><font face="monospace, monospace">''''''''''''''''''''''''''''</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Code using contextvars can be refactored into subroutines without affecting the semantics.  For instance::</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">    assi = cvar.assign(new_value)</font></div><div><font face="monospace, monospace">    def apply():</font></div><div><font face="monospace, monospace">        assi.__enter__()</font></div><div><font face="monospace, monospace">    assert cvar.value == "the default value"</font></div><div><font face="monospace, monospace">    apply()</font></div><div><font face="monospace, monospace">    assert cvar.value is new_value</font></div><div><font face="monospace, monospace">    assi.__exit__()</font></div><div><font face="monospace, monospace">    assert cvar.value == "the default value"</font></div><div><font face="monospace, monospace">    </font></div><div><font face="monospace, monospace">    </font></div><div><font face="monospace, monospace">Or similarly in an asynchronous context where ``await`` expressions are used. The subroutine can now be a coroutine::</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">    assi = cvar.assign(new_value)</font></div><div><font face="monospace, monospace">    async def apply():</font></div><div><font face="monospace, monospace">        assi.__enter__()</font></div><div><font face="monospace, monospace">    assert cvar.value == "the default value"</font></div><div><font face="monospace, monospace">    await apply()</font></div><div><font face="monospace, monospace">    assert cvar.value is new_value</font></div><div><font face="monospace, monospace">    assi.__exit__()</font></div><div><font face="monospace, monospace">    assert cvar.value == "the default value"</font></div><div><font face="monospace, monospace">    </font></div><div><font face="monospace, monospace">    </font></div><div><font face="monospace, monospace">Or when the subroutine is a generator::</font></div><div><font face="monospace, monospace"> </font></div><div><font face="monospace, monospace">    def apply():</font></div><div><font face="monospace, monospace">        yield</font></div><div><font face="monospace, monospace">        assi.__enter__()</font></div><div><font face="monospace, monospace">        </font></div><div><font face="monospace, monospace">        </font></div><div><font face="monospace, monospace">which is called using ``yield from apply()`` or with calls to ``next`` or ``.send``. This is discussed further in later sections.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Semantics for generators and generator-based coroutines</font></div><div><font face="monospace, monospace">''''''''''''''''''''''''''''''<wbr>'''''''''''''''''''''''''</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Generators, coroutines and async generators act as subroutines in much the same way that normal functions do. However, they have the additional possibility of being suspended by ``yield`` expressions. Assignment contexts entered inside a generator are normally preserved across yields::</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">    def genfunc():</font></div><div><font face="monospace, monospace">        with cvar.assign(new_value):</font></div><div><font face="monospace, monospace">            assert cvar.value is new_value</font></div><div><font face="monospace, monospace">            yield</font></div><div><font face="monospace, monospace">            assert cvar.value is new_value</font></div><div><font face="monospace, monospace">    g = genfunc()       </font></div><div><font face="monospace, monospace">    next(g)</font></div><div><font face="monospace, monospace">    assert cvar.value == "the default value"</font></div><div><font face="monospace, monospace">    with cvar.assign(another_value):</font></div><div><font face="monospace, monospace">        next(g)</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">However, the outer context visible to the generator may change state across yields::</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">    def genfunc():</font></div><div><font face="monospace, monospace">        assert cvar.value is value2</font></div><div><font face="monospace, monospace">        yield</font></div><div><font face="monospace, monospace">        assert cvar.value is value1</font></div><div><font face="monospace, monospace">        yield</font></div><div><font face="monospace, monospace">        with cvar.assign(value3):</font></div><div><font face="monospace, monospace">            assert cvar.value is value3</font></div><div><font face="monospace, monospace">           </font></div><div><font face="monospace, monospace">    with cvar.assign(value1):</font></div><div><font face="monospace, monospace">        g = genfunc()</font></div><div><font face="monospace, monospace">        with cvar.assign(value2):</font></div><div><font face="monospace, monospace">            next(g)</font></div><div><font face="monospace, monospace">        next(g)</font></div><div><font face="monospace, monospace">        next(g)</font></div><div><font face="monospace, monospace">        assert cvar.value is value1</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Similar semantics apply to async generators defined by ``async def ... yield ...`` ). </font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">By default, values assigned inside a generator do not leak through yields to the code that drives the generator. However, the assignment contexts entered and left open inside the generator *do* become visible outside the generator after the generator has finished with a ``StopIteration`` or another exception::</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">    assi = cvar.assign(new_value)</font></div><div><font face="monospace, monospace">    def genfunc():</font></div><div><font face="monospace, monospace">        yield</font></div><div><font face="monospace, monospace">        assi.__enter__():</font></div><div><font face="monospace, monospace">        yield</font></div><div><font face="monospace, monospace">    </font></div><div><font face="monospace, monospace">    g = genfunc()</font></div><div><font face="monospace, monospace">    assert cvar.value == "the default value"</font></div><div><font face="monospace, monospace">    next(g)</font></div><div><font face="monospace, monospace">    assert cvar.value == "the default value"</font></div><div><font face="monospace, monospace">    next(g)  # assi.__enter__() is called here</font></div><div><font face="monospace, monospace">    assert cvar.value == "the default value"</font></div><div><font face="monospace, monospace">    next(g)</font></div><div><font face="monospace, monospace">    assert cvar.value is new_value</font></div><div><font face="monospace, monospace">    assi.__exit__()</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Special functionality for framework authors</font></div><div><font face="monospace, monospace">------------------------------<wbr>-------------</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Frameworks, such as ``asyncio`` or third-party libraries, can use additional functionality in ``contextvars`` to achieve the desired semantics in cases which are not determined by the Python interpreter. Some of the semantics described in this section are also afterwards used to describe the internal implementation.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Leaking yields</font></div><div><font face="monospace, monospace">''''''''''''''</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Using the ``contextvars.leaking_yields`` decorator, one can choose to leak the context through ``yield`` expressions into the outer context that drives the generator::</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">    @contextvars.leaking_yields</font></div><div><font face="monospace, monospace">    def genfunc():</font></div><div><font face="monospace, monospace">        assert cvar.value == "outer"</font></div><div><font face="monospace, monospace">        with cvar.assign("inner"):</font></div><div><font face="monospace, monospace">            yield</font></div><div><font face="monospace, monospace">            assert cvar.value == "inner"</font></div><div><font face="monospace, monospace">        assert cvar.value == "outer"</font></div><div><font face="monospace, monospace">        </font></div><div><font face="monospace, monospace">    g = genfunc():</font></div><div><font face="monospace, monospace">    with cvar.assign("outer"):</font></div><div><font face="monospace, monospace">        assert cvar.value == "outer"</font></div><div><font face="monospace, monospace">        next(g)</font></div><div><font face="monospace, monospace">        assert cvar.value == "inner"</font></div><div><font face="monospace, monospace">        next(g)</font></div><div><font face="monospace, monospace">        assert cvar.value == "outer"</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div></div></div></blockquote><div><br></div><div><div class="gmail_default" style="font-family:monospace,monospace">​Unfortunately, we actually need a third kind of generator semantics, something like this:</div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace">@​contextvars.caller_context</div><div class="gmail_default" style="font-family:monospace,monospace">def genfunc():</div><div class="gmail_default" style="font-family:monospace,monospace">    assert cvar.value is the_value</div><div class="gmail_default" style="font-family:monospace,monospace">    yield</div><div class="gmail_default" style="font-family:monospace,monospace">    assert cvar.value is the_value</div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace">with cvar.assign(the_value):</div><div class="gmail_default" style="font-family:monospace,monospace">    gen = genfunc()</div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace">next(gen)</div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace">with cvar.assign(1234567890):</div><div class="gmail_default" style="font-family:monospace,monospace">    try:</div><div class="gmail_default" style="font-family:monospace,monospace">        next(gen)</div><div class="gmail_default" style="font-family:monospace,monospace">    except StopIteration:</div><div class="gmail_default" style="font-family:monospace,monospace">        pass</div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace">Nick, Yury and I (and Nathaniel, Guido, Jim, ...?) somehow just narrowly missed the reasons for this in discussions related to PEP 550. Perhaps because we had mostly been looking at it from an async angle.</div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace">[In addition to this, all context changes (Assignment __enter__ or __exit__) would be leaked out when the generator finishes iff there are no outer context changes. If there are outer context changes, an attempt to leak changes will fail. (I will probably need to explain this better).]</div><div class="gmail_default" style="font-family:monospace,monospace"><br></div></div><div>​</div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;padding-left:1ex;border-left-color:rgb(204,204,204);border-left-width:1px;border-left-style:solid"><div dir="ltr"><div><div><font face="monospace, monospace"></font></div><div><font face="monospace, monospace">Capturing contextvar assignments</font></div><div><font face="monospace, monospace">''''''''''''''''''''''''''''''<wbr>''</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Using ``contextvars.capture()``, one can capture the assignment contexts that are entered by a block of code. The changes applied by the block of code can then be reverted and subsequently reapplied, even in another context::</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">    assert cvar1.value is None # default</font></div><div><font face="monospace, monospace">    assert cvar2.value is None # default</font></div><div><font face="monospace, monospace">    assi1 = cvar1.assign(value1)</font></div><div><font face="monospace, monospace">    assi2 = cvar1.assign(value2)</font></div><div><font face="monospace, monospace">    with contextvars.capture() as delta:</font></div><div><font face="monospace, monospace">        assi1.__enter__()</font></div><div><font face="monospace, monospace">        with cvar2.assign("not captured"):</font></div><div><font face="monospace, monospace">            assert cvar2.value is "not captured"</font></div><div><font face="monospace, monospace">        assi2.__enter__()</font></div><div><font face="monospace, monospace">    assert cvar1.value is value2</font></div><div><font face="monospace, monospace">    delta.revert()</font></div><div><font face="monospace, monospace">    assert cvar1.value is None</font></div><div><font face="monospace, monospace">    assert cvar2.value is None</font></div><div><font face="monospace, monospace">    ...</font></div><div><font face="monospace, monospace">    with cvar1.assign(1), cvar2.assign(2):</font></div><div><font face="monospace, monospace">        delta.reapply()</font></div><div><font face="monospace, monospace">        assert cvar1.value is value2</font></div><div><font face="monospace, monospace">        assert cvar2.value == 2</font></div><div><font face="monospace, monospace">    </font></div><div><font face="monospace, monospace">    </font></div><div><font face="monospace, monospace">However, reapplying the "delta" if its net contents include deassignments may not be possible (see also Implementation and Open Issues).</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Getting a snapshot of context state</font></div><div><font face="monospace, monospace">''''''''''''''''''''''''''''''<wbr>'''''</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">The function ``contextvars.get_local_state(<wbr>)`` returns an object representing the applied assignments to all context-local variables in the context where the function is called. This can be seen as equivalent to using ``contextvars.capture()`` to capture all context changes from the beginning of execution. The returned object supports methods ``.revert()`` and ``reapply()`` as above.</font></div><div><font face="monospace, monospace"><br></font></div></div></div></blockquote><div><br></div><div><div class="gmail_default" style="font-family:monospace,monospace">​We will probably need also a ``use()`` method (or another name) here. That would return a context manager that applies the full context on __enter__ and reapplies the previous one on __exit__.</div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace"> </div></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;padding-left:1ex;border-left-color:rgb(204,204,204);border-left-width:1px;border-left-style:solid"><div dir="ltr"><div><div><font face="monospace, monospace"></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Running code in a clean state</font></div><div><font face="monospace, monospace">'''''''''''''''''''''''''''''</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Although it is possible to revert all applied context changes using the above primitives, a more convenient way to run a block of code in a clean context is provided::</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">    with context_vars.clean_context():</font></div><div><font face="monospace, monospace">        # here, all context vars start off with their default values</font></div><div><font face="monospace, monospace">    # here, the state is back to what it was before the with block.</font></div><div><font face="monospace, monospace">    </font></div><div><font face="monospace, monospace"><br>​</font></div></div></div></blockquote><div><br></div><div><div class="gmail_default" style="font-family:monospace,monospace">​As an additional tool, there could be contextvars.callback:</div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace">@contextvars.callback</div><div class="gmail_default" style="font-family:monospace,monospace">def some_callback():</div><div class="gmail_default" style="font-family:monospace,monospace">    # do stuff</div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace">This would provide some of the functionality of this PEP if callbacks are used, so that the callback would be run with the same context as the code that creates the callback. </div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace">The implementation of this would be essentially:</div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace">def callback(func):</div><div class="gmail_default" style="font-family:monospace,monospace">    context = contextvars.get_local_context(<wbr>):</div><div class="gmail_default" style="font-family:monospace,monospace">    def wrapped(*args, **kwargs):</div><div class="gmail_default" style="font-family:monospace,monospace">        with context.use():</div><div class="gmail_default" style="font-family:monospace,monospace">            func(*args, **kwargs</div><div class="gmail_default" style="font-family:monospace,monospace">    return wrapped</div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace">With some trickery this might allow an async framework based on callbacks instead of coroutines to use context arguments. But using this m​ight be a bit awkward sometimes. A contextlib.ExitStack might help here.</div></div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;padding-left:1ex;border-left-color:rgb(204,204,204);border-left-width:1px;border-left-style:solid"><div dir="ltr"><div><div><font face="monospace, monospace">​</font></div></div></div></blockquote><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;padding-left:1ex;border-left-color:rgb(204,204,204);border-left-width:1px;border-left-style:solid"><div dir="ltr"><div><div><font face="monospace, monospace"></font></div><div><font face="monospace, monospace">Implementation</font></div><div><font face="monospace, monospace">--------------</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">This section describes to a variable level of detail how the described semantics can be implemented. At present, an implementation aimed at simplicity but sufficient features is described. More details will be added later. </font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Alternatively, a somewhat more complicated implementation offers minor additional features while adding some performance overhead and requiring more code in the implementation.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Data structures and implementation of the core concept</font></div><div><font face="monospace, monospace">''''''''''''''''''''''''''''''<wbr>''''''''''''''''''''''''</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Each thread of the Python interpreter keeps its on stack of ``contextvars.Assignment`` objects, each having a pointer to the previous (outer) assignment like in a linked list. The local state (also returned by ``contextvars.get_local_state(<wbr>)``) then consists of a reference to the top of the stack and a pointer/weak reference to the bottom of the stack. This allows efficient stack manipulations. An object produced by ``contextvars.capture()`` is similar, but refers to only a part of the stack with the bottom reference pointing to the top of the stack as it was in the beginning of the capture block. </font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Now, the stack evolves according to the assignment ``__enter__`` and ``__exit__`` methods. For example::</font></div><div><font face="monospace, monospace">    </font></div><div><font face="monospace, monospace">    cvar1 = contextvars.Var()</font></div><div><font face="monospace, monospace">    cvar2 = contextvars.Var()</font></div><div><font face="monospace, monospace">    # stack: []</font></div><div><font face="monospace, monospace">    assert cvar1.value is None</font></div><div><font face="monospace, monospace">    assert cvar2.value is None</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">    with cvar1.assign("outer"):</font></div><div><font face="monospace, monospace">        # stack: [Assignment(cvar1, "outer")]</font></div><div><font face="monospace, monospace">        assert cvar1.value == "outer"</font></div><div><font face="monospace, monospace">        </font></div><div><font face="monospace, monospace">        with cvar1.assign("inner"):</font></div><div><font face="monospace, monospace">            # stack: [Assignment(cvar1, "outer"), </font></div><div><font face="monospace, monospace">            #         Assignment(cvar1, "inner")]</font></div><div><font face="monospace, monospace">            assert cvar1.value == "inner"</font></div><div><font face="monospace, monospace">            </font></div><div><font face="monospace, monospace">            with cvar2.assign("hello"):</font></div><div><font face="monospace, monospace">                # stack: [Assignment(cvar1, "outer"), </font></div><div><font face="monospace, monospace">                #         Assignment(cvar1, "inner"),</font></div><div><font face="monospace, monospace">                #         Assignment(cvar2, "hello")]</font></div><div><font face="monospace, monospace">                assert cvar2.value == "hello"</font></div><div><font face="monospace, monospace">               </font></div><div><font face="monospace, monospace">            # stack: [Assignment(cvar1, "outer"), </font></div><div><font face="monospace, monospace">            #         Assignment(cvar1, "inner")]</font></div><div><font face="monospace, monospace">            assert cvar1.value == "inner"</font></div><div><font face="monospace, monospace">            assert cvar2.value is None</font></div><div><font face="monospace, monospace">            </font></div><div><font face="monospace, monospace">        # stack: [Assignment(cvar1, "outer")]</font></div><div><font face="monospace, monospace">        assert cvar1.value == "outer"</font></div><div><font face="monospace, monospace">        </font></div><div><font face="monospace, monospace">    # stack: []</font></div><div><font face="monospace, monospace">    assert cvar1.value is None</font></div><div><font face="monospace, monospace">    assert cvar2.value is None</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Getting a value from the context using ``cvar1.value`` can be implemented as finding the topmost occurrence of a ``cvar1`` assignment on the stack and returning the value there, or the default value if no assignment is found on the stack. However, this can be optimized to instead be an O(1) operation in most cases. Still, even searching through the stack may be reasonably fast since these stacks are not intended to grow very large.</font></div></div></div></blockquote><div><br></div><div><div class="gmail_default" style="font-family:monospace,monospace">​I will still need to explain the O(1) algorithm, but one nice thing is that an implementation like micropython does not necessarily need to include that optimization.​</div></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;padding-left:1ex;border-left-color:rgb(204,204,204);border-left-width:1px;border-left-style:solid"><div dir="ltr"><div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">The above description is already sufficient for implementing the core concept. Suspendable frames require some additional attention, as explained in the following.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Implementation of generator and coroutine semantics</font></div><div><font face="monospace, monospace">''''''''''''''''''''''''''''''<wbr>'''''''''''''''''''''</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Within generators, coroutines and async generators, assignments and deassignments are handled in exactly the same way as anywhere else. However, some changes are needed in the builtin generator methods ``send``, ``__next__``, ``throw`` and ``close``. Here is the Python equivalent of the changes needed in ``send`` for a generator (here ``_old_send`` refers to the behavior in Python 3.6)::</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">    def send(self, value):</font></div><div><font face="monospace, monospace">        # if decorated with contextvars.leaking_yields</font></div><div><font face="monospace, monospace">        if self.gi_contextvars is LEAK:</font></div><div><font face="monospace, monospace">            # nothing needs to be done to leak context through yields :)</font></div><div><font face="monospace, monospace">            return self._old_send(value)</font></div><div><font face="monospace, monospace">        try:</font></div><div><font face="monospace, monospace">            with contextvars.capture() as delta:</font></div><div><font face="monospace, monospace">                if self.gi_contextvars: </font></div><div><font face="monospace, monospace">                    # non-zero captured content from previous iteration</font></div><div><font face="monospace, monospace">                    self.gi_contextvars.reapply()</font></div><div><font face="monospace, monospace">                ret = self._old_send(value)</font></div><div><font face="monospace, monospace">        except Exception:</font></div><div><font face="monospace, monospace">            raise</font></div><div><font face="monospace, monospace">        else:</font></div><div><font face="monospace, monospace">            # suspending, revert context changes but </font></div><div><font face="monospace, monospace">            delta.revert()</font></div><div><font face="monospace, monospace">            self.gi_contextvars = delta</font></div><div><font face="monospace, monospace">        return ret</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">The corresponding modifications to the other methods is essentially identical. The same applies to coroutines and async generators. </font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">For code that does not use ``contextvars``, the additions are O(1) and essentially reduce to a couple of pointer comparisons. For code that does use ``contextvars``, the additions are still O(1) in most cases.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">More on implementation</font></div><div><font face="monospace, monospace">''''''''''''''''''''''</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">The rest of the functionality, including ``contextvars.leaking_yields``<wbr>, contextvars.capture()``, ``contextvars.get_local_state(<wbr>)`` and ``contextvars.clean_context()`<wbr>` are in fact quite straightforward to implement, but their implementation will be discussed further in later versions of this proposal. Caching of assigned values is somewhat more complicated, and will be discussed later, but it seems that most cases should achieve O(1) complexity.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Backwards compatibility</font></div><div><font face="monospace, monospace">=======================</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">There are no *direct* backwards-compatibility concerns, since a completely new feature is proposed. </font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">However, various traditional uses of thread-local storage may need a smooth transition to ``contextvars`` so they can be concurrency-safe. There are several approaches to this, including emulating task-local storage with a little bit of help from async frameworks. A fully general implementation cannot be provided, because the desired semantics may depend on the design of the framework. </font></div><div><font face="monospace, monospace"><br></font></div></div></div></blockquote><div><br></div><div><div class="gmail_default" style="font-family:monospace,monospace">​I have a preliminary design for this, but probably doesn't need to be in this PEP.​</div></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;padding-left:1ex;border-left-color:rgb(204,204,204);border-left-width:1px;border-left-style:solid"><div dir="ltr"><div><div><font face="monospace, monospace"></font></div><div><font face="monospace, monospace">Another way to deal with the transition is for code to first look for a context created using ``contextvars``. If that fails because a new-style context has not been set or because the code runs on an older Python version, a fallback to thread-local storage is used.</font></div><div><font face="monospace, monospace"><br></font></div></div></div></blockquote><div><br></div><div><div class="gmail_default" style="font-family:monospace,monospace">​If context variables are renamed context arguments, then there could be a settable variant called a context variable (could also be a third-party thing on top of context arguments, depending on what is done with decimal contexts).​</div></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;padding-left:1ex;border-left-color:rgb(204,204,204);border-left-width:1px;border-left-style:solid"><div dir="ltr"><div><div><font face="monospace, monospace"></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Open Issues</font></div><div><font face="monospace, monospace">===========</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Out-of-order de-assignments</font></div><div><font face="monospace, monospace">---------------------------</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">In this proposal, all variable deassignments are made in the opposite order compared to the preceding assignments. This has two useful properties: it encourages using ``with`` statements to define assignment scope and has a tendency to catch errors early (forgetting a ``.__exit__()`` call often results in a meaningful error. To have this as a requirement requirement is beneficial also in terms of implementation simplicity and performance. Nevertheless, allowing out-of-order context exits is not completely out of the question, and reasonable implementation strategies for that do exist.</font></div><div><font face="monospace, monospace"><br>​</font></div></div></div></blockquote><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;padding-left:1ex;border-left-color:rgb(204,204,204);border-left-width:1px;border-left-style:solid"><div dir="ltr"><div><div><font face="monospace, monospace"></font></div><div><font face="monospace, monospace">Rejected Ideas</font></div><div><font face="monospace, monospace">==============</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Dynamic scoping linked to subroutine scopes</font></div><div><font face="monospace, monospace">------------------------------<wbr>-------------</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">The scope of value visibility should not be determined by the way the code is refactored into subroutines. It is necessary to have per-variable control of the assignment scope.</font></div><div><font face="monospace, monospace"><br></font></div></div></div></blockquote><div><br></div><div><div class="gmail_default" style="font-family:monospace,monospace">​In fact, in early sketches, my approach was closer to this. The context variables (or async variables) were stored in frame locals in a namespace called `__async__` and they were propagated through subroutine calls to callees. But this introduces problems when new scope layers are added, and ended up being more complicated (and slightly similar to PEP 550).</div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace">Anyway, for starters, this was a glimpse of the changes I have planned, and open for discussion.</div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace">-- Koos</div><div class="gmail_default" style="font-family:monospace,monospace"><br></div><div class="gmail_default" style="font-family:monospace,monospace">​-- <br></div></div></div><div class="m_-715260322859149822m_-3053719038438635560m_-4501325205128632483gmail_signature">+ Koos Zevenhoven + <a href="http://twitter.com/k7hoven" target="_blank">http://twitter.com/k7hoven</a> +</div>
</div></div>