CINXE.COM
PEP 568 – Generator-sensitivity for Context Variables | peps.python.org
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="color-scheme" content="light dark"> <title>PEP 568 – Generator-sensitivity for Context Variables | peps.python.org</title> <link rel="shortcut icon" href="../_static/py.png"> <link rel="canonical" href="https://peps.python.org/pep-0568/"> <link rel="stylesheet" href="../_static/style.css" type="text/css"> <link rel="stylesheet" href="../_static/mq.css" type="text/css"> <link rel="stylesheet" href="../_static/pygments.css" type="text/css" media="(prefers-color-scheme: light)" id="pyg-light"> <link rel="stylesheet" href="../_static/pygments_dark.css" type="text/css" media="(prefers-color-scheme: dark)" id="pyg-dark"> <link rel="alternate" type="application/rss+xml" title="Latest PEPs" href="https://peps.python.org/peps.rss"> <meta property="og:title" content='PEP 568 – Generator-sensitivity for Context Variables | peps.python.org'> <meta property="og:description" content="Context variables provide a generic mechanism for tracking dynamic, context-local state, similar to thread-local storage but generalized to cope work with other kinds of thread-like contexts, such as asyncio Tasks. PEP 550 proposed a mechanism for conte..."> <meta property="og:type" content="website"> <meta property="og:url" content="https://peps.python.org/pep-0568/"> <meta property="og:site_name" content="Python Enhancement Proposals (PEPs)"> <meta property="og:image" content="https://peps.python.org/_static/og-image.png"> <meta property="og:image:alt" content="Python PEPs"> <meta property="og:image:width" content="200"> <meta property="og:image:height" content="200"> <meta name="description" content="Context variables provide a generic mechanism for tracking dynamic, context-local state, similar to thread-local storage but generalized to cope work with other kinds of thread-like contexts, such as asyncio Tasks. PEP 550 proposed a mechanism for conte..."> <meta name="theme-color" content="#3776ab"> </head> <body> <svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <symbol id="svg-sun-half" viewBox="0 0 24 24" pointer-events="all"> <title>Following system colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="9"></circle> <path d="M12 3v18m0-12l4.65-4.65M12 14.3l7.37-7.37M12 19.6l8.85-8.85"></path> </svg> </symbol> <symbol id="svg-moon" viewBox="0 0 24 24" pointer-events="all"> <title>Selected dark colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path> </svg> </symbol> <symbol id="svg-sun" viewBox="0 0 24 24" pointer-events="all"> <title>Selected light colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="5"></circle> <line x1="12" y1="1" x2="12" y2="3"></line> <line x1="12" y1="21" x2="12" y2="23"></line> <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line> <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line> <line x1="1" y1="12" x2="3" y2="12"></line> <line x1="21" y1="12" x2="23" y2="12"></line> <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line> <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line> </svg> </symbol> </svg> <script> document.documentElement.dataset.colour_scheme = localStorage.getItem("colour_scheme") || "auto" </script> <section id="pep-page-section"> <header> <h1>Python Enhancement Proposals</h1> <ul class="breadcrumbs"> <li><a href="https://www.python.org/" title="The Python Programming Language">Python</a> » </li> <li><a href="../pep-0000/">PEP Index</a> » </li> <li>PEP 568</li> </ul> <button id="colour-scheme-cycler" onClick="setColourScheme(nextColourScheme())"> <svg aria-hidden="true" class="colour-scheme-icon-when-auto"><use href="#svg-sun-half"></use></svg> <svg aria-hidden="true" class="colour-scheme-icon-when-dark"><use href="#svg-moon"></use></svg> <svg aria-hidden="true" class="colour-scheme-icon-when-light"><use href="#svg-sun"></use></svg> <span class="visually-hidden">Toggle light / dark / auto colour theme</span> </button> </header> <article> <section id="pep-content"> <h1 class="page-title">PEP 568 – Generator-sensitivity for Context Variables</h1> <dl class="rfc2822 field-list simple"> <dt class="field-odd">Author<span class="colon">:</span></dt> <dd class="field-odd">Nathaniel J. Smith <njs at pobox.com></dd> <dt class="field-even">Status<span class="colon">:</span></dt> <dd class="field-even"><abbr title="Inactive draft that may be taken up again at a later time">Deferred</abbr></dd> <dt class="field-odd">Type<span class="colon">:</span></dt> <dd class="field-odd"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd> <dt class="field-even">Created<span class="colon">:</span></dt> <dd class="field-even">04-Jan-2018</dd> <dt class="field-odd">Python-Version<span class="colon">:</span></dt> <dd class="field-odd">3.8</dd> <dt class="field-even">Post-History<span class="colon">:</span></dt> <dd class="field-even"><p></p></dd> </dl> <hr class="docutils" /> <section id="contents"> <details><summary>Table of Contents</summary><ul class="simple"> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#rationale">Rationale</a></li> <li><a class="reference internal" href="#high-level-summary">High-level summary</a></li> <li><a class="reference internal" href="#specification">Specification</a><ul> <li><a class="reference internal" href="#review-of-pep-567">Review of PEP 567</a></li> <li><a class="reference internal" href="#changes-from-pep-567-to-this-pep">Changes from PEP 567 to this PEP</a></li> </ul> </li> <li><a class="reference internal" href="#comparison-to-pep-550">Comparison to PEP 550</a></li> <li><a class="reference internal" href="#implementation-notes">Implementation notes</a></li> <li><a class="reference internal" href="#copyright">Copyright</a></li> </ul> </details></section> <section id="abstract"> <h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2> <p>Context variables provide a generic mechanism for tracking dynamic, context-local state, similar to thread-local storage but generalized to cope work with other kinds of thread-like contexts, such as asyncio Tasks. <a class="pep reference internal" href="../pep-0550/" title="PEP 550 – Execution Context">PEP 550</a> proposed a mechanism for context-local state that was also sensitive to generator context, but this was pretty complicated, so the BDFL requested it be simplified. The result was <a class="pep reference internal" href="../pep-0567/" title="PEP 567 – Context Variables">PEP 567</a>, which is targeted for inclusion in 3.7. This PEP then extends <a class="pep reference internal" href="../pep-0567/" title="PEP 567 – Context Variables">PEP 567</a>’s machinery to add generator context sensitivity.</p> <p>This PEP is starting out in the “deferred” status, because there isn’t enough time to give it proper consideration before the 3.7 feature freeze. The only goal <em>right now</em> is to understand what would be required to add generator context sensitivity in 3.8, so that we can avoid shipping something in 3.7 that would rule it out by accident. (Ruling it out on purpose can wait until 3.8 ;-).)</p> </section> <section id="rationale"> <h2><a class="toc-backref" href="#rationale" role="doc-backlink">Rationale</a></h2> <p>[Currently the point of this PEP is just to understand <em>how</em> this would work, with discussion of <em>whether</em> it’s a good idea deferred until after the 3.7 feature freeze. So rationale is TBD.]</p> </section> <section id="high-level-summary"> <h2><a class="toc-backref" href="#high-level-summary" role="doc-backlink">High-level summary</a></h2> <p>Instead of holding a single <code class="docutils literal notranslate"><span class="pre">Context</span></code>, the threadstate now holds a <code class="docutils literal notranslate"><span class="pre">ChainMap</span></code> of <code class="docutils literal notranslate"><span class="pre">Context</span></code>s. <code class="docutils literal notranslate"><span class="pre">ContextVar.get</span></code> and <code class="docutils literal notranslate"><span class="pre">ContextVar.set</span></code> are backed by the <code class="docutils literal notranslate"><span class="pre">ChainMap</span></code>. Generators and async generators each have an associated <code class="docutils literal notranslate"><span class="pre">Context</span></code> that they push onto the <code class="docutils literal notranslate"><span class="pre">ChainMap</span></code> while they’re running to isolate their context-local changes from their callers, though this can be overridden in cases like <code class="docutils literal notranslate"><span class="pre">@contextlib.contextmanager</span></code> where “leaking” context changes from the generator into its caller is desirable.</p> </section> <section id="specification"> <h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2> <section id="review-of-pep-567"> <h3><a class="toc-backref" href="#review-of-pep-567" role="doc-backlink">Review of PEP 567</a></h3> <p>Let’s start by reviewing how <a class="pep reference internal" href="../pep-0567/" title="PEP 567 – Context Variables">PEP 567</a> works, and then in the next section we’ll describe the differences.</p> <p>In <a class="pep reference internal" href="../pep-0567/" title="PEP 567 – Context Variables">PEP 567</a>, a <code class="docutils literal notranslate"><span class="pre">Context</span></code> is a <code class="docutils literal notranslate"><span class="pre">Mapping</span></code> from <code class="docutils literal notranslate"><span class="pre">ContextVar</span></code> objects to arbitrary values. In our pseudo-code here we’ll pretend that it uses a <code class="docutils literal notranslate"><span class="pre">dict</span></code> for backing storage. (The real implementation uses a HAMT, which is semantically equivalent to a <code class="docutils literal notranslate"><span class="pre">dict</span></code> but with different performance trade-offs.):</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">Context</span><span class="p">(</span><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">Mapping</span><span class="p">):</span> <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">_data</span> <span class="o">=</span> <span class="p">{}</span> <span class="bp">self</span><span class="o">.</span><span class="n">_in_use</span> <span class="o">=</span> <span class="kc">False</span> <span class="k">def</span><span class="w"> </span><span class="fm">__getitem__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_data</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="k">def</span><span class="w"> </span><span class="fm">__iter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="k">return</span> <span class="nb">iter</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_data</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="fm">__len__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_data</span><span class="p">)</span> </pre></div> </div> <p>At any given moment, the threadstate holds a current <code class="docutils literal notranslate"><span class="pre">Context</span></code> (initialized to an empty <code class="docutils literal notranslate"><span class="pre">Context</span></code> when the threadstate is created); we can use <code class="docutils literal notranslate"><span class="pre">Context.run</span></code> to temporarily switch the current <code class="docutils literal notranslate"><span class="pre">Context</span></code>:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Context.run</span> <span class="k">def</span><span class="w"> </span><span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">fn</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_in_use</span><span class="p">:</span> <span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span><span class="s2">"Context already in use"</span><span class="p">)</span> <span class="n">tstate</span> <span class="o">=</span> <span class="n">get_thread_state</span><span class="p">()</span> <span class="n">old_context</span> <span class="o">=</span> <span class="n">tstate</span><span class="o">.</span><span class="n">current_context</span> <span class="n">tstate</span><span class="o">.</span><span class="n">current_context</span> <span class="o">=</span> <span class="bp">self</span> <span class="bp">self</span><span class="o">.</span><span class="n">_in_use</span> <span class="o">=</span> <span class="kc">True</span> <span class="k">try</span><span class="p">:</span> <span class="k">return</span> <span class="n">fn</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="k">finally</span><span class="p">:</span> <span class="n">state</span><span class="o">.</span><span class="n">current_context</span> <span class="o">=</span> <span class="n">old_context</span> <span class="bp">self</span><span class="o">.</span><span class="n">_in_use</span> <span class="o">=</span> <span class="kc">False</span> </pre></div> </div> <p>We can fetch a shallow copy of the current <code class="docutils literal notranslate"><span class="pre">Context</span></code> by calling <code class="docutils literal notranslate"><span class="pre">copy_context</span></code>; this is commonly used when spawning a new task, so that the child task can inherit context from its parent:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">copy_context</span><span class="p">():</span> <span class="n">tstate</span> <span class="o">=</span> <span class="n">get_thread_state</span><span class="p">()</span> <span class="n">new_context</span> <span class="o">=</span> <span class="n">Context</span><span class="p">()</span> <span class="n">new_context</span><span class="o">.</span><span class="n">_data</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="n">tstate</span><span class="o">.</span><span class="n">current_context</span><span class="p">)</span> <span class="k">return</span> <span class="n">new_context</span> </pre></div> </div> <p>In practice, what end users generally work with is <code class="docutils literal notranslate"><span class="pre">ContextVar</span></code> objects, which also provide the only way to mutate a <code class="docutils literal notranslate"><span class="pre">Context</span></code>. They work with a utility class <code class="docutils literal notranslate"><span class="pre">Token</span></code>, which can be used to restore a <code class="docutils literal notranslate"><span class="pre">ContextVar</span></code> to its previous value:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">Token</span><span class="p">:</span> <span class="n">MISSING</span> <span class="o">=</span> <span class="n">sentinel_value</span><span class="p">()</span> <span class="c1"># Note: constructor is private</span> <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">,</span> <span class="n">var</span><span class="p">,</span> <span class="n">old_value</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">_context</span> <span class="o">=</span> <span class="n">context</span> <span class="bp">self</span><span class="o">.</span><span class="n">var</span> <span class="o">=</span> <span class="n">var</span> <span class="bp">self</span><span class="o">.</span><span class="n">old_value</span> <span class="o">=</span> <span class="n">old_value</span> <span class="c1"># XX: PEP 567 currently makes this a method on ContextVar, but</span> <span class="c1"># I'm going to propose it switch to this API because it's simpler.</span> <span class="k">def</span><span class="w"> </span><span class="nf">reset</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="c1"># XX: should we allow token reuse?</span> <span class="c1"># XX: should we allow tokens to be used if the saved</span> <span class="c1"># context is no longer active?</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">old_value</span> <span class="ow">is</span> <span class="bp">self</span><span class="o">.</span><span class="n">MISSING</span><span class="p">:</span> <span class="k">del</span> <span class="bp">self</span><span class="o">.</span><span class="n">_context</span><span class="o">.</span><span class="n">_data</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">context_var</span><span class="p">]</span> <span class="k">else</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_context</span><span class="o">.</span><span class="n">_data</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">context_var</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">old_value</span> <span class="c1"># XX: the handling of defaults here uses the simplified proposal from</span> <span class="c1"># https://mail.python.org/pipermail/python-dev/2018-January/151596.html</span> <span class="c1"># This can be updated to whatever we settle on, it was just less</span> <span class="c1"># typing this way :-)</span> <span class="k">class</span><span class="w"> </span><span class="nc">ContextVar</span><span class="p">:</span> <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span> <span class="o">=</span> <span class="n">default</span> <span class="k">def</span><span class="w"> </span><span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="n">context</span> <span class="o">=</span> <span class="n">get_thread_state</span><span class="p">()</span><span class="o">.</span><span class="n">current_context</span> <span class="k">return</span> <span class="n">context</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">set</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">new_value</span><span class="p">):</span> <span class="n">context</span> <span class="o">=</span> <span class="n">get_thread_state</span><span class="p">()</span><span class="o">.</span><span class="n">current_context</span> <span class="n">token</span> <span class="o">=</span> <span class="n">Token</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">Token</span><span class="o">.</span><span class="n">MISSING</span><span class="p">))</span> <span class="n">context</span><span class="o">.</span><span class="n">_data</span><span class="p">[</span><span class="bp">self</span><span class="p">]</span> <span class="o">=</span> <span class="n">new_value</span> <span class="k">return</span> <span class="n">token</span> </pre></div> </div> </section> <section id="changes-from-pep-567-to-this-pep"> <h3><a class="toc-backref" href="#changes-from-pep-567-to-this-pep" role="doc-backlink">Changes from PEP 567 to this PEP</a></h3> <p>In general, <code class="docutils literal notranslate"><span class="pre">Context</span></code> remains the same. However, now instead of holding a single <code class="docutils literal notranslate"><span class="pre">Context</span></code> object, the threadstate stores a stack of them. This stack acts just like a <code class="docutils literal notranslate"><span class="pre">collections.ChainMap</span></code>, so we’ll use that in our pseudocode. <code class="docutils literal notranslate"><span class="pre">Context.run</span></code> then becomes:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Context.run</span> <span class="k">def</span><span class="w"> </span><span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">fn</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_in_use</span><span class="p">:</span> <span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span><span class="s2">"Context already in use"</span><span class="p">)</span> <span class="n">tstate</span> <span class="o">=</span> <span class="n">get_thread_state</span><span class="p">()</span> <span class="n">old_context_stack</span> <span class="o">=</span> <span class="n">tstate</span><span class="o">.</span><span class="n">current_context_stack</span> <span class="n">tstate</span><span class="o">.</span><span class="n">current_context_stack</span> <span class="o">=</span> <span class="n">ChainMap</span><span class="p">([</span><span class="bp">self</span><span class="p">])</span> <span class="c1"># changed</span> <span class="bp">self</span><span class="o">.</span><span class="n">_in_use</span> <span class="o">=</span> <span class="kc">True</span> <span class="k">try</span><span class="p">:</span> <span class="k">return</span> <span class="n">fn</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="k">finally</span><span class="p">:</span> <span class="n">state</span><span class="o">.</span><span class="n">current_context_stack</span> <span class="o">=</span> <span class="n">old_context_stack</span> <span class="bp">self</span><span class="o">.</span><span class="n">_in_use</span> <span class="o">=</span> <span class="kc">False</span> </pre></div> </div> <p>Aside from some updated variables names (e.g., <code class="docutils literal notranslate"><span class="pre">tstate.current_context</span></code> → <code class="docutils literal notranslate"><span class="pre">tstate.current_context_stack</span></code>), the only change here is on the marked line, which now wraps the context in a <code class="docutils literal notranslate"><span class="pre">ChainMap</span></code> before stashing it in the threadstate.</p> <p>We also add a <code class="docutils literal notranslate"><span class="pre">Context.push</span></code> method, which is almost exactly like <code class="docutils literal notranslate"><span class="pre">Context.run</span></code>, except that it temporarily pushes the <code class="docutils literal notranslate"><span class="pre">Context</span></code> onto the existing stack, instead of temporarily replacing the whole stack:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Context.push</span> <span class="k">def</span><span class="w"> </span><span class="nf">push</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">fn</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_in_use</span><span class="p">:</span> <span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span><span class="s2">"Context already in use"</span><span class="p">)</span> <span class="n">tstate</span> <span class="o">=</span> <span class="n">get_thread_state</span><span class="p">()</span> <span class="n">tstate</span><span class="o">.</span><span class="n">current_context_stack</span><span class="o">.</span><span class="n">maps</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span> <span class="c1"># different from run</span> <span class="bp">self</span><span class="o">.</span><span class="n">_in_use</span> <span class="o">=</span> <span class="kc">True</span> <span class="k">try</span><span class="p">:</span> <span class="k">return</span> <span class="n">fn</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="k">finally</span><span class="p">:</span> <span class="n">tstate</span><span class="o">.</span><span class="n">current_context_stack</span><span class="o">.</span><span class="n">maps</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="c1"># different from run</span> <span class="bp">self</span><span class="o">.</span><span class="n">_in_use</span> <span class="o">=</span> <span class="kc">False</span> </pre></div> </div> <p>In most cases, we don’t expect <code class="docutils literal notranslate"><span class="pre">push</span></code> to be used directly; instead, it will be used implicitly by generators. Specifically, every generator object and async generator object gains a new attribute <code class="docutils literal notranslate"><span class="pre">.context</span></code>. When an (async) generator object is created, this attribute is initialized to an empty <code class="docutils literal notranslate"><span class="pre">Context</span></code> (<code class="docutils literal notranslate"><span class="pre">self.context</span> <span class="pre">=</span> <span class="pre">Context()</span></code>). This is a mutable attribute; it can be changed by user code. But trying to set it to anything that isn’t a <code class="docutils literal notranslate"><span class="pre">Context</span></code> object or <code class="docutils literal notranslate"><span class="pre">None</span></code> will raise an error.</p> <p>Whenever we enter an generator via <code class="docutils literal notranslate"><span class="pre">__next__</span></code>, <code class="docutils literal notranslate"><span class="pre">send</span></code>, <code class="docutils literal notranslate"><span class="pre">throw</span></code>, or <code class="docutils literal notranslate"><span class="pre">close</span></code>, or enter an async generator by calling one of those methods on its <code class="docutils literal notranslate"><span class="pre">__anext__</span></code>, <code class="docutils literal notranslate"><span class="pre">asend</span></code>, <code class="docutils literal notranslate"><span class="pre">athrow</span></code>, or <code class="docutils literal notranslate"><span class="pre">aclose</span></code> coroutines, then its <code class="docutils literal notranslate"><span class="pre">.context</span></code> attribute is checked, and if non-<code class="docutils literal notranslate"><span class="pre">None</span></code>, is automatically pushed:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># GeneratorType.__next__</span> <span class="k">def</span><span class="w"> </span><span class="fm">__next__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">context</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">context</span><span class="o">.</span><span class="n">push</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">__real_next__</span><span class="p">)</span> <span class="k">else</span><span class="p">:</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">__real_next__</span><span class="p">()</span> </pre></div> </div> <p>While we don’t expect people to use <code class="docutils literal notranslate"><span class="pre">Context.push</span></code> often, making it a public API preserves the principle that a generator can always be rewritten as an explicit iterator class with equivalent semantics.</p> <p>Also, we modify <code class="docutils literal notranslate"><span class="pre">contextlib.(async)contextmanager</span></code> to always set its (async) generator objects’ <code class="docutils literal notranslate"><span class="pre">.context</span></code> attribute to <code class="docutils literal notranslate"><span class="pre">None</span></code>:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># contextlib._GeneratorContextManagerBase.__init__</span> <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">func</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">kwds</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">gen</span> <span class="o">=</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwds</span><span class="p">)</span> <span class="bp">self</span><span class="o">.</span><span class="n">gen</span><span class="o">.</span><span class="n">context</span> <span class="o">=</span> <span class="kc">None</span> <span class="c1"># added</span> <span class="o">...</span> </pre></div> </div> <p>This makes sure that code like this continues to work as expected:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@contextmanager</span> <span class="k">def</span><span class="w"> </span><span class="nf">decimal_precision</span><span class="p">(</span><span class="n">prec</span><span class="p">):</span> <span class="k">with</span> <span class="n">decimal</span><span class="o">.</span><span class="n">localcontext</span><span class="p">()</span> <span class="k">as</span> <span class="n">ctx</span><span class="p">:</span> <span class="n">ctx</span><span class="o">.</span><span class="n">prec</span> <span class="o">=</span> <span class="n">prec</span> <span class="k">yield</span> <span class="k">with</span> <span class="n">decimal_precision</span><span class="p">(</span><span class="mi">2</span><span class="p">):</span> <span class="o">...</span> </pre></div> </div> <p>The general idea here is that by default, every generator object gets its own local context, but if users want to explicitly get some other behavior then they can do that.</p> <p>Otherwise, things mostly work as before, except that we go through and swap everything to use the threadstate <code class="docutils literal notranslate"><span class="pre">ChainMap</span></code> instead of the threadstate <code class="docutils literal notranslate"><span class="pre">Context</span></code>. In full detail:</p> <p>The <code class="docutils literal notranslate"><span class="pre">copy_context</span></code> function now returns a flattened copy of the “effective” context. (As an optimization, the implementation might choose to do this flattening lazily, but if so this will be made invisible to the user.) Compared to our previous implementation above, the only change here is that <code class="docutils literal notranslate"><span class="pre">tstate.current_context</span></code> has been replaced with <code class="docutils literal notranslate"><span class="pre">tstate.current_context_stack</span></code>:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">copy_context</span><span class="p">()</span> <span class="o">-></span> <span class="n">Context</span><span class="p">:</span> <span class="n">tstate</span> <span class="o">=</span> <span class="n">get_thread_state</span><span class="p">()</span> <span class="n">new_context</span> <span class="o">=</span> <span class="n">Context</span><span class="p">()</span> <span class="n">new_context</span><span class="o">.</span><span class="n">_data</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="n">tstate</span><span class="o">.</span><span class="n">current_context_stack</span><span class="p">)</span> <span class="k">return</span> <span class="n">new_context</span> </pre></div> </div> <p><code class="docutils literal notranslate"><span class="pre">Token</span></code> is unchanged, and the changes to <code class="docutils literal notranslate"><span class="pre">ContextVar.get</span></code> are trivial:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># ContextVar.get</span> <span class="k">def</span><span class="w"> </span><span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="n">context_stack</span> <span class="o">=</span> <span class="n">get_thread_state</span><span class="p">()</span><span class="o">.</span><span class="n">current_context_stack</span> <span class="k">return</span> <span class="n">context_stack</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span><span class="p">)</span> </pre></div> </div> <p><code class="docutils literal notranslate"><span class="pre">ContextVar.set</span></code> is a little more interesting: instead of going through the <code class="docutils literal notranslate"><span class="pre">ChainMap</span></code> machinery like everything else, it always mutates the top <code class="docutils literal notranslate"><span class="pre">Context</span></code> in the stack, and – crucially! – sets up the returned <code class="docutils literal notranslate"><span class="pre">Token</span></code> to restore <em>its</em> state later. This allows us to avoid accidentally “promoting” values between different levels in the stack, as would happen if we did <code class="docutils literal notranslate"><span class="pre">old</span> <span class="pre">=</span> <span class="pre">var.get();</span> <span class="pre">...;</span> <span class="pre">var.set(old)</span></code>:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># ContextVar.set</span> <span class="k">def</span><span class="w"> </span><span class="nf">set</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">new_value</span><span class="p">):</span> <span class="n">top_context</span> <span class="o">=</span> <span class="n">get_thread_state</span><span class="p">()</span><span class="o">.</span><span class="n">current_context_stack</span><span class="o">.</span><span class="n">maps</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="n">token</span> <span class="o">=</span> <span class="n">Token</span><span class="p">(</span><span class="n">top_context</span><span class="p">,</span> <span class="bp">self</span><span class="p">,</span> <span class="n">top_context</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">Token</span><span class="o">.</span><span class="n">MISSING</span><span class="p">))</span> <span class="n">top_context</span><span class="o">.</span><span class="n">_data</span><span class="p">[</span><span class="bp">self</span><span class="p">]</span> <span class="o">=</span> <span class="n">new_value</span> <span class="k">return</span> <span class="n">token</span> </pre></div> </div> <p>And finally, to allow for introspection of the full context stack, we provide a new function <code class="docutils literal notranslate"><span class="pre">contextvars.get_context_stack</span></code>:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">get_context_stack</span><span class="p">()</span> <span class="o">-></span> <span class="n">List</span><span class="p">[</span><span class="n">Context</span><span class="p">]:</span> <span class="k">return</span> <span class="nb">list</span><span class="p">(</span><span class="n">get_thread_state</span><span class="p">()</span><span class="o">.</span><span class="n">current_context_stack</span><span class="o">.</span><span class="n">maps</span><span class="p">)</span> </pre></div> </div> <p>That’s all.</p> </section> </section> <section id="comparison-to-pep-550"> <h2><a class="toc-backref" href="#comparison-to-pep-550" role="doc-backlink">Comparison to PEP 550</a></h2> <p>The main difference from <a class="pep reference internal" href="../pep-0550/" title="PEP 550 – Execution Context">PEP 550</a> is that it reified what we’re calling “contexts” and “context stacks” as two different concrete types (<code class="docutils literal notranslate"><span class="pre">LocalContext</span></code> and <code class="docutils literal notranslate"><span class="pre">ExecutionContext</span></code> respectively). This led to lots of confusion about what the differences were, and which object should be used in which places. This proposal simplifies things by only reifying the <code class="docutils literal notranslate"><span class="pre">Context</span></code>, which is “just a dict”, and makes the “context stack” an unnamed feature of the interpreter’s runtime state – though it is still possible to introspect it using <code class="docutils literal notranslate"><span class="pre">get_context_stack</span></code>, for debugging and other purposes.</p> </section> <section id="implementation-notes"> <h2><a class="toc-backref" href="#implementation-notes" role="doc-backlink">Implementation notes</a></h2> <p><code class="docutils literal notranslate"><span class="pre">Context</span></code> will continue to use a HAMT-based mapping structure under the hood instead of <code class="docutils literal notranslate"><span class="pre">dict</span></code>, since we expect that calls to <code class="docutils literal notranslate"><span class="pre">copy_context</span></code> are much more common than <code class="docutils literal notranslate"><span class="pre">ContextVar.set</span></code>. In almost all cases, <code class="docutils literal notranslate"><span class="pre">copy_context</span></code> will find that there’s only one <code class="docutils literal notranslate"><span class="pre">Context</span></code> in the stack (because it’s rare for generators to spawn new tasks), and can simply re-use it directly; in other cases HAMTs are cheap to merge and this can be done lazily.</p> <p>Rather than using an actual <code class="docutils literal notranslate"><span class="pre">ChainMap</span></code> object, we’ll represent the context stack using some appropriate structure – the most appropriate options are probably either a bare <code class="docutils literal notranslate"><span class="pre">list</span></code> with the “top” of the stack being the end of the list so we can use <code class="docutils literal notranslate"><span class="pre">push</span></code>/<code class="docutils literal notranslate"><span class="pre">pop</span></code>, or else an intrusive linked list (<code class="docutils literal notranslate"><span class="pre">PyThreadState</span></code> → <code class="docutils literal notranslate"><span class="pre">Context</span></code> → <code class="docutils literal notranslate"><span class="pre">Context</span></code> → …), with the “top” of the stack at the beginning of the list to allow efficient push/pop.</p> <p>A critical optimization in <a class="pep reference internal" href="../pep-0567/" title="PEP 567 – Context Variables">PEP 567</a> is the caching of values inside <code class="docutils literal notranslate"><span class="pre">ContextVar</span></code>. Switching from a single context to a context stack makes this a little bit more complicated, but not too much. Currently, we invalidate the cache whenever the threadstate’s current <code class="docutils literal notranslate"><span class="pre">Context</span></code> changes (on thread switch, and when entering/exiting <code class="docutils literal notranslate"><span class="pre">Context.run</span></code>). The simplest approach here would be to invalidate the cache whenever stack changes (on thread switch, when entering/exiting <code class="docutils literal notranslate"><span class="pre">Context.run</span></code>, and when entering/leaving <code class="docutils literal notranslate"><span class="pre">Context.push</span></code>). The main effect of this is that iterating a generator will invalidate the cache. It seems unlikely that this will cause serious problems, but if it does, then I think it can be avoided with a cleverer cache key that recognizes that pushing and then popping a <code class="docutils literal notranslate"><span class="pre">Context</span></code> returns the threadstate to its previous state. (Idea: store the cache key for a particular stack configuration in the topmost <code class="docutils literal notranslate"><span class="pre">Context</span></code>.)</p> <p>It seems unavoidable in this design that uncached <code class="docutils literal notranslate"><span class="pre">get</span></code> will be O(n), where n is the size of the context stack. However, n will generally be very small – it’s roughly the number of nested generators, so usually n=1, and it will be extremely rare to see n greater than, say, 5. At worst, n is bounded by the recursion limit. In addition, we can expect that in most cases of deep generator recursion, most of the <code class="docutils literal notranslate"><span class="pre">Context</span></code>s in the stack will be empty, and thus can be skipped extremely quickly during lookup. And for repeated lookups the caching mechanism will kick in. So it’s probably possible to construct some extreme case where this causes performance problems, but ordinary code should be essentially unaffected.</p> </section> <section id="copyright"> <h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2> <p>This document has been placed in the public domain.</p> </section> </section> <hr class="docutils" /> <p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0568.rst">https://github.com/python/peps/blob/main/peps/pep-0568.rst</a></p> <p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0568.rst">2025-02-01 08:55:40 GMT</a></p> </article> <nav id="pep-sidebar"> <h2>Contents</h2> <ul> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#rationale">Rationale</a></li> <li><a class="reference internal" href="#high-level-summary">High-level summary</a></li> <li><a class="reference internal" href="#specification">Specification</a><ul> <li><a class="reference internal" href="#review-of-pep-567">Review of PEP 567</a></li> <li><a class="reference internal" href="#changes-from-pep-567-to-this-pep">Changes from PEP 567 to this PEP</a></li> </ul> </li> <li><a class="reference internal" href="#comparison-to-pep-550">Comparison to PEP 550</a></li> <li><a class="reference internal" href="#implementation-notes">Implementation notes</a></li> <li><a class="reference internal" href="#copyright">Copyright</a></li> </ul> <br> <a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0568.rst">Page Source (GitHub)</a> </nav> </section> <script src="../_static/colour_scheme.js"></script> <script src="../_static/wrap_tables.js"></script> <script src="../_static/sticky_banner.js"></script> </body> </html>