1004 lines
89 KiB
HTML
1004 lines
89 KiB
HTML
|
||
<!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 567 – Context Variables | peps.python.org</title>
|
||
<link rel="shortcut icon" href="../_static/py.png">
|
||
<link rel="canonical" href="https://peps.python.org/pep-0567/">
|
||
<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 567 – Context Variables | peps.python.org'>
|
||
<meta property="og:description" content="This PEP proposes a new contextvars module and a set of new CPython C APIs to support context variables. This concept is similar to thread-local storage (TLS), but, unlike TLS, it also allows correctly keeping track of values per asynchronous task, e.g...">
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:url" content="https://peps.python.org/pep-0567/">
|
||
<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="This PEP proposes a new contextvars module and a set of new CPython C APIs to support context variables. This concept is similar to thread-local storage (TLS), but, unlike TLS, it also allows correctly keeping track of values per asynchronous task, e.g...">
|
||
<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 567</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 567 – Context Variables</h1>
|
||
<dl class="rfc2822 field-list simple">
|
||
<dt class="field-odd">Author<span class="colon">:</span></dt>
|
||
<dd class="field-odd">Yury Selivanov <yury at edgedb.com></dd>
|
||
<dt class="field-even">Status<span class="colon">:</span></dt>
|
||
<dd class="field-even"><abbr title="Accepted and implementation complete, or no longer active">Final</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">12-Dec-2017</dd>
|
||
<dt class="field-odd">Python-Version<span class="colon">:</span></dt>
|
||
<dd class="field-odd">3.7</dd>
|
||
<dt class="field-even">Post-History<span class="colon">:</span></dt>
|
||
<dd class="field-even">12-Dec-2017, 28-Dec-2017, 16-Jan-2018</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="#api-design-and-implementation-revisions">API Design and Implementation Revisions</a></li>
|
||
<li><a class="reference internal" href="#rationale">Rationale</a></li>
|
||
<li><a class="reference internal" href="#introduction">Introduction</a></li>
|
||
<li><a class="reference internal" href="#specification">Specification</a><ul>
|
||
<li><a class="reference internal" href="#contextvars-contextvar">contextvars.ContextVar</a></li>
|
||
<li><a class="reference internal" href="#contextvars-token">contextvars.Token</a></li>
|
||
<li><a class="reference internal" href="#contextvars-context">contextvars.Context</a></li>
|
||
<li><a class="reference internal" href="#asyncio">asyncio</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#implementation">Implementation</a></li>
|
||
<li><a class="reference internal" href="#summary-of-the-new-apis">Summary of the New APIs</a><ul>
|
||
<li><a class="reference internal" href="#python-api">Python API</a></li>
|
||
<li><a class="reference internal" href="#c-api">C API</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a><ul>
|
||
<li><a class="reference internal" href="#replicating-threading-local-interface">Replicating threading.local() interface</a></li>
|
||
<li><a class="reference internal" href="#replacing-token-with-contextvar-unset">Replacing Token with ContextVar.unset()</a></li>
|
||
<li><a class="reference internal" href="#having-token-reset-instead-of-contextvar-reset">Having Token.reset() instead of ContextVar.reset()</a></li>
|
||
<li><a class="reference internal" href="#making-context-objects-picklable">Making Context objects picklable</a></li>
|
||
<li><a class="reference internal" href="#making-context-a-mutablemapping">Making Context a MutableMapping</a></li>
|
||
<li><a class="reference internal" href="#having-initial-values-for-contextvars">Having initial values for ContextVars</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></li>
|
||
<li><a class="reference internal" href="#examples">Examples</a><ul>
|
||
<li><a class="reference internal" href="#converting-code-that-uses-threading-local">Converting code that uses threading.local()</a></li>
|
||
<li><a class="reference internal" href="#offloading-execution-to-other-threads">Offloading execution to other threads</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
|
||
<li><a class="reference internal" href="#acceptance">Acceptance</a></li>
|
||
<li><a class="reference internal" href="#references">References</a></li>
|
||
<li><a class="reference internal" href="#acknowledgments">Acknowledgments</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>This PEP proposes a new <code class="docutils literal notranslate"><span class="pre">contextvars</span></code> module and a set of new
|
||
CPython C APIs to support context variables. This concept is
|
||
similar to thread-local storage (TLS), but, unlike TLS, it also allows
|
||
correctly keeping track of values per asynchronous task, e.g.
|
||
<code class="docutils literal notranslate"><span class="pre">asyncio.Task</span></code>.</p>
|
||
<p>This proposal is a simplified version of <a class="pep reference internal" href="../pep-0550/" title="PEP 550 – Execution Context">PEP 550</a>. The key
|
||
difference is that this PEP is concerned only with solving the case
|
||
for asynchronous tasks, not for generators. There are no proposed
|
||
modifications to any built-in types or to the interpreter.</p>
|
||
<p>This proposal is not strictly related to Python Context Managers.
|
||
Although it does provide a mechanism that can be used by Context
|
||
Managers to store their state.</p>
|
||
</section>
|
||
<section id="api-design-and-implementation-revisions">
|
||
<h2><a class="toc-backref" href="#api-design-and-implementation-revisions" role="doc-backlink">API Design and Implementation Revisions</a></h2>
|
||
<p>In <strong>Python 3.7.1</strong> the signatures of all context variables
|
||
C APIs were <strong>changed</strong> to use <code class="docutils literal notranslate"><span class="pre">PyObject</span> <span class="pre">*</span></code> pointers instead
|
||
of <code class="docutils literal notranslate"><span class="pre">PyContext</span> <span class="pre">*</span></code>, <code class="docutils literal notranslate"><span class="pre">PyContextVar</span> <span class="pre">*</span></code>, and <code class="docutils literal notranslate"><span class="pre">PyContextToken</span> <span class="pre">*</span></code>,
|
||
e.g.:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="o">//</span> <span class="ow">in</span> <span class="mf">3.7.0</span><span class="p">:</span>
|
||
<span class="n">PyContext</span> <span class="o">*</span><span class="n">PyContext_New</span><span class="p">(</span><span class="n">void</span><span class="p">);</span>
|
||
|
||
<span class="o">//</span> <span class="ow">in</span> <span class="mf">3.7.1</span><span class="o">+</span><span class="p">:</span>
|
||
<span class="n">PyObject</span> <span class="o">*</span><span class="n">PyContext_New</span><span class="p">(</span><span class="n">void</span><span class="p">);</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>See <a class="footnote-reference brackets" href="#id12" id="id1">[6]</a> for more details. The <a class="reference internal" href="#c-api">C API</a> section of this PEP was
|
||
updated to reflect the change.</p>
|
||
</section>
|
||
<section id="rationale">
|
||
<h2><a class="toc-backref" href="#rationale" role="doc-backlink">Rationale</a></h2>
|
||
<p>Thread-local variables are insufficient for asynchronous tasks that
|
||
execute concurrently in the same OS thread. Any context manager that
|
||
saves and restores a context value using <code class="docutils literal notranslate"><span class="pre">threading.local()</span></code> will
|
||
have its context values bleed to other code unexpectedly when used
|
||
in async/await code.</p>
|
||
<p>A few examples where having a working context local storage for
|
||
asynchronous code is desirable:</p>
|
||
<ul class="simple">
|
||
<li>Context managers like <code class="docutils literal notranslate"><span class="pre">decimal</span></code> contexts and <code class="docutils literal notranslate"><span class="pre">numpy.errstate</span></code>.</li>
|
||
<li>Request-related data, such as security tokens and request
|
||
data in web applications, language context for <code class="docutils literal notranslate"><span class="pre">gettext</span></code>, etc.</li>
|
||
<li>Profiling, tracing, and logging in large code bases.</li>
|
||
</ul>
|
||
</section>
|
||
<section id="introduction">
|
||
<h2><a class="toc-backref" href="#introduction" role="doc-backlink">Introduction</a></h2>
|
||
<p>The PEP proposes a new mechanism for managing context variables.
|
||
The key classes involved in this mechanism are <code class="docutils literal notranslate"><span class="pre">contextvars.Context</span></code>
|
||
and <code class="docutils literal notranslate"><span class="pre">contextvars.ContextVar</span></code>. The PEP also proposes some policies
|
||
for using the mechanism around asynchronous tasks.</p>
|
||
<p>The proposed mechanism for accessing context variables uses the
|
||
<code class="docutils literal notranslate"><span class="pre">ContextVar</span></code> class. A module (such as <code class="docutils literal notranslate"><span class="pre">decimal</span></code>) that wishes to
|
||
use the new mechanism should:</p>
|
||
<ul class="simple">
|
||
<li>declare a module-global variable holding a <code class="docutils literal notranslate"><span class="pre">ContextVar</span></code> to
|
||
serve as a key;</li>
|
||
<li>access the current value via the <code class="docutils literal notranslate"><span class="pre">get()</span></code> method on the
|
||
key variable;</li>
|
||
<li>modify the current value via the <code class="docutils literal notranslate"><span class="pre">set()</span></code> method on the
|
||
key variable.</li>
|
||
</ul>
|
||
<p>The notion of “current value” deserves special consideration:
|
||
different asynchronous tasks that exist and execute concurrently
|
||
may have different values for the same key. This idea is well known
|
||
from thread-local storage but in this case the locality of the value is
|
||
not necessarily bound to a thread. Instead, there is the notion of the
|
||
“current <code class="docutils literal notranslate"><span class="pre">Context</span></code>” which is stored in thread-local storage.
|
||
Manipulation of the current context is the responsibility of the
|
||
task framework, e.g. asyncio.</p>
|
||
<p>A <code class="docutils literal notranslate"><span class="pre">Context</span></code> is a mapping of <code class="docutils literal notranslate"><span class="pre">ContextVar</span></code> objects to their values.
|
||
The <code class="docutils literal notranslate"><span class="pre">Context</span></code> itself exposes the <code class="docutils literal notranslate"><span class="pre">abc.Mapping</span></code> interface
|
||
(not <code class="docutils literal notranslate"><span class="pre">abc.MutableMapping</span></code>!), so it cannot be modified directly.
|
||
To set a new value for a context variable in a <code class="docutils literal notranslate"><span class="pre">Context</span></code> object,
|
||
the user needs to:</p>
|
||
<ul class="simple">
|
||
<li>make the <code class="docutils literal notranslate"><span class="pre">Context</span></code> object “current” using the <code class="docutils literal notranslate"><span class="pre">Context.run()</span></code>
|
||
method;</li>
|
||
<li>use <code class="docutils literal notranslate"><span class="pre">ContextVar.set()</span></code> to set a new value for the context
|
||
variable.</li>
|
||
</ul>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">ContextVar.get()</span></code> method looks for the variable in the current
|
||
<code class="docutils literal notranslate"><span class="pre">Context</span></code> object using <code class="docutils literal notranslate"><span class="pre">self</span></code> as a key.</p>
|
||
<p>It is not possible to get a direct reference to the current <code class="docutils literal notranslate"><span class="pre">Context</span></code>
|
||
object, but it is possible to obtain a shallow copy of it using the
|
||
<code class="docutils literal notranslate"><span class="pre">contextvars.copy_context()</span></code> function. This ensures that the
|
||
<em>caller</em> of <code class="docutils literal notranslate"><span class="pre">Context.run()</span></code> is the sole owner of its <code class="docutils literal notranslate"><span class="pre">Context</span></code>
|
||
object.</p>
|
||
</section>
|
||
<section id="specification">
|
||
<h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2>
|
||
<p>A new standard library module <code class="docutils literal notranslate"><span class="pre">contextvars</span></code> is added with the
|
||
following APIs:</p>
|
||
<ol class="arabic simple">
|
||
<li>The <code class="docutils literal notranslate"><span class="pre">copy_context()</span> <span class="pre">-></span> <span class="pre">Context</span></code> function is used to get a copy of
|
||
the current <code class="docutils literal notranslate"><span class="pre">Context</span></code> object for the current OS thread.</li>
|
||
<li>The <code class="docutils literal notranslate"><span class="pre">ContextVar</span></code> class to declare and access context variables.</li>
|
||
<li>The <code class="docutils literal notranslate"><span class="pre">Context</span></code> class encapsulates context state. Every OS thread
|
||
stores a reference to its current <code class="docutils literal notranslate"><span class="pre">Context</span></code> instance.
|
||
It is not possible to control that reference directly.
|
||
Instead, the <code class="docutils literal notranslate"><span class="pre">Context.run(callable,</span> <span class="pre">*args,</span> <span class="pre">**kwargs)</span></code> method is
|
||
used to run Python code in another context.</li>
|
||
</ol>
|
||
<section id="contextvars-contextvar">
|
||
<h3><a class="toc-backref" href="#contextvars-contextvar" role="doc-backlink">contextvars.ContextVar</a></h3>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">ContextVar</span></code> class has the following constructor signature:
|
||
<code class="docutils literal notranslate"><span class="pre">ContextVar(name,</span> <span class="pre">*,</span> <span class="pre">default=_NO_DEFAULT)</span></code>. The <code class="docutils literal notranslate"><span class="pre">name</span></code> parameter
|
||
is used for introspection and debug purposes, and is exposed
|
||
as a read-only <code class="docutils literal notranslate"><span class="pre">ContextVar.name</span></code> attribute. The <code class="docutils literal notranslate"><span class="pre">default</span></code>
|
||
parameter is optional. Example:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Declare a context variable 'var' with the default value 42.</span>
|
||
<span class="n">var</span> <span class="o">=</span> <span class="n">ContextVar</span><span class="p">(</span><span class="s1">'var'</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="mi">42</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>(The <code class="docutils literal notranslate"><span class="pre">_NO_DEFAULT</span></code> is an internal sentinel object used to
|
||
detect if the default value was provided.)</p>
|
||
<p><code class="docutils literal notranslate"><span class="pre">ContextVar.get(default=_NO_DEFAULT)</span></code> returns a value for
|
||
the context variable for 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"># Get the value of `var`.</span>
|
||
<span class="n">var</span><span class="o">.</span><span class="n">get</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>If there is no value for the variable in the current context,
|
||
<code class="docutils literal notranslate"><span class="pre">ContextVar.get()</span></code> will:</p>
|
||
<ul class="simple">
|
||
<li>return the value of the <em>default</em> argument of the <code class="docutils literal notranslate"><span class="pre">get()</span></code> method,
|
||
if provided; or</li>
|
||
<li>return the default value for the context variable, if provided; or</li>
|
||
<li>raise a <code class="docutils literal notranslate"><span class="pre">LookupError</span></code>.</li>
|
||
</ul>
|
||
<p><code class="docutils literal notranslate"><span class="pre">ContextVar.set(value)</span> <span class="pre">-></span> <span class="pre">Token</span></code> is used to set a new value for
|
||
the context variable in 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"># Set the variable 'var' to 1 in the current context.</span>
|
||
<span class="n">var</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="docutils literal notranslate"><span class="pre">ContextVar.reset(token)</span></code> is used to reset the variable in the
|
||
current context to the value it had before the <code class="docutils literal notranslate"><span class="pre">set()</span></code> operation
|
||
that created the <code class="docutils literal notranslate"><span class="pre">token</span></code> (or to remove the variable if it was
|
||
not set):</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Assume: var.get(None) is None</span>
|
||
|
||
<span class="c1"># Set 'var' to 1:</span>
|
||
<span class="n">token</span> <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
|
||
<span class="k">try</span><span class="p">:</span>
|
||
<span class="c1"># var.get() == 1</span>
|
||
<span class="k">finally</span><span class="p">:</span>
|
||
<span class="n">var</span><span class="o">.</span><span class="n">reset</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
|
||
|
||
<span class="c1"># After reset: var.get(None) is None,</span>
|
||
<span class="c1"># i.e. 'var' was removed from the current context.</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">ContextVar.reset()</span></code> method raises:</p>
|
||
<ul class="simple">
|
||
<li>a <code class="docutils literal notranslate"><span class="pre">ValueError</span></code> if it is called with a token object created
|
||
by another variable;</li>
|
||
<li>a <code class="docutils literal notranslate"><span class="pre">ValueError</span></code> if the current <code class="docutils literal notranslate"><span class="pre">Context</span></code> object does not match
|
||
the one where the token object was created;</li>
|
||
<li>a <code class="docutils literal notranslate"><span class="pre">RuntimeError</span></code> if the token object has already been used once
|
||
to reset the variable.</li>
|
||
</ul>
|
||
</section>
|
||
<section id="contextvars-token">
|
||
<h3><a class="toc-backref" href="#contextvars-token" role="doc-backlink">contextvars.Token</a></h3>
|
||
<p><code class="docutils literal notranslate"><span class="pre">contextvars.Token</span></code> is an opaque object that should be used to
|
||
restore the <code class="docutils literal notranslate"><span class="pre">ContextVar</span></code> to its previous value, or to remove it from
|
||
the context if the variable was not set before. It can be created
|
||
only by calling <code class="docutils literal notranslate"><span class="pre">ContextVar.set()</span></code>.</p>
|
||
<p>For debug and introspection purposes it has:</p>
|
||
<ul class="simple">
|
||
<li>a read-only attribute <code class="docutils literal notranslate"><span class="pre">Token.var</span></code> pointing to the variable
|
||
that created the token;</li>
|
||
<li>a read-only attribute <code class="docutils literal notranslate"><span class="pre">Token.old_value</span></code> set to the value the
|
||
variable had before the <code class="docutils literal notranslate"><span class="pre">set()</span></code> call, or to <code class="docutils literal notranslate"><span class="pre">Token.MISSING</span></code>
|
||
if the variable wasn’t set before.</li>
|
||
</ul>
|
||
</section>
|
||
<section id="contextvars-context">
|
||
<h3><a class="toc-backref" href="#contextvars-context" role="doc-backlink">contextvars.Context</a></h3>
|
||
<p><code class="docutils literal notranslate"><span class="pre">Context</span></code> object is a mapping of context variables to values.</p>
|
||
<p><code class="docutils literal notranslate"><span class="pre">Context()</span></code> creates an empty context. To get a copy of the current
|
||
<code class="docutils literal notranslate"><span class="pre">Context</span></code> for the current OS thread, use the
|
||
<code class="docutils literal notranslate"><span class="pre">contextvars.copy_context()</span></code> method:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">ctx</span> <span class="o">=</span> <span class="n">contextvars</span><span class="o">.</span><span class="n">copy_context</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>To run Python code in some <code class="docutils literal notranslate"><span class="pre">Context</span></code>, use <code class="docutils literal notranslate"><span class="pre">Context.run()</span></code>
|
||
method:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">ctx</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">function</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Any changes to any context variables that <code class="docutils literal notranslate"><span class="pre">function</span></code> causes will
|
||
be contained in the <code class="docutils literal notranslate"><span class="pre">ctx</span></code> context:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">var</span> <span class="o">=</span> <span class="n">ContextVar</span><span class="p">(</span><span class="s1">'var'</span><span class="p">)</span>
|
||
<span class="n">var</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'spam'</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
|
||
<span class="c1"># 'var' was set to 'spam' before</span>
|
||
<span class="c1"># calling 'copy_context()' and 'ctx.run(main)', so:</span>
|
||
<span class="c1"># var.get() == ctx[var] == 'spam'</span>
|
||
|
||
<span class="n">var</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'ham'</span><span class="p">)</span>
|
||
|
||
<span class="c1"># Now, after setting 'var' to 'ham':</span>
|
||
<span class="c1"># var.get() == ctx[var] == 'ham'</span>
|
||
|
||
<span class="n">ctx</span> <span class="o">=</span> <span class="n">copy_context</span><span class="p">()</span>
|
||
|
||
<span class="c1"># Any changes that the 'main' function makes to 'var'</span>
|
||
<span class="c1"># will be contained in 'ctx'.</span>
|
||
<span class="n">ctx</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">)</span>
|
||
|
||
<span class="c1"># The 'main()' function was run in the 'ctx' context,</span>
|
||
<span class="c1"># so changes to 'var' are contained in it:</span>
|
||
<span class="c1"># ctx[var] == 'ham'</span>
|
||
|
||
<span class="c1"># However, outside of 'ctx', 'var' is still set to 'spam':</span>
|
||
<span class="c1"># var.get() == 'spam'</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="docutils literal notranslate"><span class="pre">Context.run()</span></code> raises a <code class="docutils literal notranslate"><span class="pre">RuntimeError</span></code> when called on the same
|
||
context object from more than one OS thread, or when called
|
||
recursively.</p>
|
||
<p><code class="docutils literal notranslate"><span class="pre">Context.copy()</span></code> returns a shallow copy of the context object.</p>
|
||
<p><code class="docutils literal notranslate"><span class="pre">Context</span></code> objects implement the <code class="docutils literal notranslate"><span class="pre">collections.abc.Mapping</span></code> ABC.
|
||
This can be used to introspect contexts:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">ctx</span> <span class="o">=</span> <span class="n">contextvars</span><span class="o">.</span><span class="n">copy_context</span><span class="p">()</span>
|
||
|
||
<span class="c1"># Print all context variables and their values in 'ctx':</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="n">ctx</span><span class="o">.</span><span class="n">items</span><span class="p">())</span>
|
||
|
||
<span class="c1"># Print the value of 'some_variable' in context 'ctx':</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="n">ctx</span><span class="p">[</span><span class="n">some_variable</span><span class="p">])</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Note that all Mapping methods, including <code class="docutils literal notranslate"><span class="pre">Context.__getitem__</span></code> and
|
||
<code class="docutils literal notranslate"><span class="pre">Context.get</span></code>, ignore default values for context variables
|
||
(i.e. <code class="docutils literal notranslate"><span class="pre">ContextVar.default</span></code>). This means that for a variable <em>var</em>
|
||
that was created with a default value and was not set in the
|
||
<em>context</em>:</p>
|
||
<ul class="simple">
|
||
<li><code class="docutils literal notranslate"><span class="pre">context[var]</span></code> raises a <code class="docutils literal notranslate"><span class="pre">KeyError</span></code>,</li>
|
||
<li><code class="docutils literal notranslate"><span class="pre">var</span> <span class="pre">in</span> <span class="pre">context</span></code> returns <code class="docutils literal notranslate"><span class="pre">False</span></code>,</li>
|
||
<li>the variable isn’t included in <code class="docutils literal notranslate"><span class="pre">context.items()</span></code>, etc.</li>
|
||
</ul>
|
||
</section>
|
||
<section id="asyncio">
|
||
<h3><a class="toc-backref" href="#asyncio" role="doc-backlink">asyncio</a></h3>
|
||
<p><code class="docutils literal notranslate"><span class="pre">asyncio</span></code> uses <code class="docutils literal notranslate"><span class="pre">Loop.call_soon()</span></code>, <code class="docutils literal notranslate"><span class="pre">Loop.call_later()</span></code>,
|
||
and <code class="docutils literal notranslate"><span class="pre">Loop.call_at()</span></code> to schedule the asynchronous execution of a
|
||
function. <code class="docutils literal notranslate"><span class="pre">asyncio.Task</span></code> uses <code class="docutils literal notranslate"><span class="pre">call_soon()</span></code> to run the
|
||
wrapped coroutine.</p>
|
||
<p>We modify <code class="docutils literal notranslate"><span class="pre">Loop.call_{at,later,soon}</span></code> and
|
||
<code class="docutils literal notranslate"><span class="pre">Future.add_done_callback()</span></code> to accept the new optional <em>context</em>
|
||
keyword-only argument, which defaults to the current context:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">call_soon</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">callback</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||
<span class="k">if</span> <span class="n">context</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||
<span class="n">context</span> <span class="o">=</span> <span class="n">contextvars</span><span class="o">.</span><span class="n">copy_context</span><span class="p">()</span>
|
||
|
||
<span class="c1"># ... some time later</span>
|
||
<span class="n">context</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">callback</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Tasks in asyncio need to maintain their own context that they inherit
|
||
from the point they were created at. <code class="docutils literal notranslate"><span class="pre">asyncio.Task</span></code> is modified
|
||
as follows:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Task</span><span class="p">:</span>
|
||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">coro</span><span class="p">):</span>
|
||
<span class="o">...</span>
|
||
<span class="c1"># Get the current context snapshot.</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">_context</span> <span class="o">=</span> <span class="n">contextvars</span><span class="o">.</span><span class="n">copy_context</span><span class="p">()</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">_loop</span><span class="o">.</span><span class="n">call_soon</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_step</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">_context</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">_step</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">exc</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||
<span class="o">...</span>
|
||
<span class="c1"># Every advance of the wrapped coroutine is done in</span>
|
||
<span class="c1"># the task's context.</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">_loop</span><span class="o">.</span><span class="n">call_soon</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_step</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">_context</span><span class="p">)</span>
|
||
<span class="o">...</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
</section>
|
||
<section id="implementation">
|
||
<h2><a class="toc-backref" href="#implementation" role="doc-backlink">Implementation</a></h2>
|
||
<p>This section explains high-level implementation details in
|
||
pseudo-code. Some optimizations are omitted to keep this section
|
||
short and clear.</p>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">Context</span></code> mapping is implemented using an immutable dictionary.
|
||
This allows for a O(1) implementation of the <code class="docutils literal notranslate"><span class="pre">copy_context()</span></code>
|
||
function. The reference implementation implements the immutable
|
||
dictionary using Hash Array Mapped Tries (HAMT); see <a class="pep reference internal" href="../pep-0550/" title="PEP 550 – Execution Context">PEP 550</a>
|
||
for analysis of HAMT performance <a class="footnote-reference brackets" href="#id7" id="id2">[1]</a>.</p>
|
||
<p>For the purposes of this section, we implement an immutable dictionary
|
||
using a copy-on-write approach and the built-in dict type:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">_ContextData</span><span class="p">:</span>
|
||
|
||
<span class="k">def</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">_mapping</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
|
||
|
||
<span class="k">def</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">_mapping</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
|
||
|
||
<span class="k">def</span> <span class="fm">__contains__</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="n">key</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_mapping</span>
|
||
|
||
<span class="k">def</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">_mapping</span><span class="p">)</span>
|
||
|
||
<span class="k">def</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">_mapping</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">set</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="n">value</span><span class="p">):</span>
|
||
<span class="n">copy</span> <span class="o">=</span> <span class="n">_ContextData</span><span class="p">()</span>
|
||
<span class="n">copy</span><span class="o">.</span><span class="n">_mapping</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_mapping</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
|
||
<span class="n">copy</span><span class="o">.</span><span class="n">_mapping</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
|
||
<span class="k">return</span> <span class="n">copy</span>
|
||
|
||
<span class="k">def</span> <span class="nf">delete</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="n">copy</span> <span class="o">=</span> <span class="n">_ContextData</span><span class="p">()</span>
|
||
<span class="n">copy</span><span class="o">.</span><span class="n">_mapping</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_mapping</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
|
||
<span class="k">del</span> <span class="n">copy</span><span class="o">.</span><span class="n">_mapping</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
|
||
<span class="k">return</span> <span class="n">copy</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Every OS thread has a reference to the current <code class="docutils literal notranslate"><span class="pre">Context</span></code> object:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">PyThreadState</span><span class="p">:</span>
|
||
<span class="n">context</span><span class="p">:</span> <span class="n">Context</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="docutils literal notranslate"><span class="pre">contextvars.Context</span></code> is a wrapper around <code class="docutils literal notranslate"><span class="pre">_ContextData</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</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="n">_data</span><span class="p">:</span> <span class="n">_ContextData</span>
|
||
<span class="n">_prev_context</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Context</span><span class="p">]</span>
|
||
|
||
<span class="k">def</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="n">_ContextData</span><span class="p">()</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">_prev_context</span> <span class="o">=</span> <span class="kc">None</span>
|
||
|
||
<span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">callable</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">_prev_context</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||
<span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span>
|
||
<span class="sa">f</span><span class="s1">'cannot enter context: </span><span class="si">{</span><span class="bp">self</span><span class="si">}</span><span class="s1"> is already entered'</span><span class="p">)</span>
|
||
|
||
<span class="n">ts</span><span class="p">:</span> <span class="n">PyThreadState</span> <span class="o">=</span> <span class="n">PyThreadState_Get</span><span class="p">()</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">_prev_context</span> <span class="o">=</span> <span class="n">ts</span><span class="o">.</span><span class="n">context</span>
|
||
<span class="k">try</span><span class="p">:</span>
|
||
<span class="n">ts</span><span class="o">.</span><span class="n">context</span> <span class="o">=</span> <span class="bp">self</span>
|
||
<span class="k">return</span> <span class="nb">callable</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">ts</span><span class="o">.</span><span class="n">context</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_prev_context</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">_prev_context</span> <span class="o">=</span> <span class="kc">None</span>
|
||
|
||
<span class="k">def</span> <span class="nf">copy</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="n">new</span> <span class="o">=</span> <span class="n">Context</span><span class="p">()</span>
|
||
<span class="n">new</span><span class="o">.</span><span class="n">_data</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_data</span>
|
||
<span class="k">return</span> <span class="n">new</span>
|
||
|
||
<span class="c1"># Implement abstract Mapping.__getitem__</span>
|
||
<span class="k">def</span> <span class="fm">__getitem__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">var</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">var</span><span class="p">]</span>
|
||
|
||
<span class="c1"># Implement abstract Mapping.__contains__</span>
|
||
<span class="k">def</span> <span class="fm">__contains__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">var</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="n">var</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_data</span>
|
||
|
||
<span class="c1"># Implement abstract Mapping.__len__</span>
|
||
<span class="k">def</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>
|
||
|
||
<span class="c1"># Implement abstract Mapping.__iter__</span>
|
||
<span class="k">def</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="c1"># The rest of the Mapping methods are implemented</span>
|
||
<span class="c1"># by collections.abc.Mapping.</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="docutils literal notranslate"><span class="pre">contextvars.copy_context()</span></code> is implemented as follows:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">copy_context</span><span class="p">():</span>
|
||
<span class="n">ts</span><span class="p">:</span> <span class="n">PyThreadState</span> <span class="o">=</span> <span class="n">PyThreadState_Get</span><span class="p">()</span>
|
||
<span class="k">return</span> <span class="n">ts</span><span class="o">.</span><span class="n">context</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="docutils literal notranslate"><span class="pre">contextvars.ContextVar</span></code> interacts with <code class="docutils literal notranslate"><span class="pre">PyThreadState.context</span></code>
|
||
directly:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">ContextVar</span><span class="p">:</span>
|
||
|
||
<span class="k">def</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="n">_NO_DEFAULT</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="nd">@property</span>
|
||
<span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_name</span>
|
||
|
||
<span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="n">_NO_DEFAULT</span><span class="p">):</span>
|
||
<span class="n">ts</span><span class="p">:</span> <span class="n">PyThreadState</span> <span class="o">=</span> <span class="n">PyThreadState_Get</span><span class="p">()</span>
|
||
<span class="k">try</span><span class="p">:</span>
|
||
<span class="k">return</span> <span class="n">ts</span><span class="o">.</span><span class="n">context</span><span class="p">[</span><span class="bp">self</span><span class="p">]</span>
|
||
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
|
||
<span class="k">pass</span>
|
||
|
||
<span class="k">if</span> <span class="n">default</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">_NO_DEFAULT</span><span class="p">:</span>
|
||
<span class="k">return</span> <span class="n">default</span>
|
||
|
||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_default</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">_NO_DEFAULT</span><span class="p">:</span>
|
||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_default</span>
|
||
|
||
<span class="k">raise</span> <span class="ne">LookupError</span>
|
||
|
||
<span class="k">def</span> <span class="nf">set</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
|
||
<span class="n">ts</span><span class="p">:</span> <span class="n">PyThreadState</span> <span class="o">=</span> <span class="n">PyThreadState_Get</span><span class="p">()</span>
|
||
|
||
<span class="n">data</span><span class="p">:</span> <span class="n">_ContextData</span> <span class="o">=</span> <span class="n">ts</span><span class="o">.</span><span class="n">context</span><span class="o">.</span><span class="n">_data</span>
|
||
<span class="k">try</span><span class="p">:</span>
|
||
<span class="n">old_value</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="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
|
||
<span class="n">old_value</span> <span class="o">=</span> <span class="n">Token</span><span class="o">.</span><span class="n">MISSING</span>
|
||
|
||
<span class="n">updated_data</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
|
||
<span class="n">ts</span><span class="o">.</span><span class="n">context</span><span class="o">.</span><span class="n">_data</span> <span class="o">=</span> <span class="n">updated_data</span>
|
||
<span class="k">return</span> <span class="n">Token</span><span class="p">(</span><span class="n">ts</span><span class="o">.</span><span class="n">context</span><span class="p">,</span> <span class="bp">self</span><span class="p">,</span> <span class="n">old_value</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">reset</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">token</span><span class="p">):</span>
|
||
<span class="k">if</span> <span class="n">token</span><span class="o">.</span><span class="n">_used</span><span class="p">:</span>
|
||
<span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span><span class="s2">"Token has already been used once"</span><span class="p">)</span>
|
||
|
||
<span class="k">if</span> <span class="n">token</span><span class="o">.</span><span class="n">_var</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">self</span><span class="p">:</span>
|
||
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
|
||
<span class="s2">"Token was created by a different ContextVar"</span><span class="p">)</span>
|
||
|
||
<span class="n">ts</span><span class="p">:</span> <span class="n">PyThreadState</span> <span class="o">=</span> <span class="n">PyThreadState_Get</span><span class="p">()</span>
|
||
<span class="k">if</span> <span class="n">token</span><span class="o">.</span><span class="n">_context</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">ts</span><span class="o">.</span><span class="n">context</span><span class="p">:</span>
|
||
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
|
||
<span class="s2">"Token was created in a different Context"</span><span class="p">)</span>
|
||
|
||
<span class="k">if</span> <span class="n">token</span><span class="o">.</span><span class="n">_old_value</span> <span class="ow">is</span> <span class="n">Token</span><span class="o">.</span><span class="n">MISSING</span><span class="p">:</span>
|
||
<span class="n">ts</span><span class="o">.</span><span class="n">context</span><span class="o">.</span><span class="n">_data</span> <span class="o">=</span> <span class="n">ts</span><span class="o">.</span><span class="n">context</span><span class="o">.</span><span class="n">_data</span><span class="o">.</span><span class="n">delete</span><span class="p">(</span><span class="n">token</span><span class="o">.</span><span class="n">_var</span><span class="p">)</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">ts</span><span class="o">.</span><span class="n">context</span><span class="o">.</span><span class="n">_data</span> <span class="o">=</span> <span class="n">ts</span><span class="o">.</span><span class="n">context</span><span class="o">.</span><span class="n">_data</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="n">token</span><span class="o">.</span><span class="n">_var</span><span class="p">,</span>
|
||
<span class="n">token</span><span class="o">.</span><span class="n">_old_value</span><span class="p">)</span>
|
||
|
||
<span class="n">token</span><span class="o">.</span><span class="n">_used</span> <span class="o">=</span> <span class="kc">True</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Note that the in the reference implementation, <code class="docutils literal notranslate"><span class="pre">ContextVar.get()</span></code>
|
||
has an internal cache for the most recent value, which allows to
|
||
bypass a hash lookup. This is similar to the optimization the
|
||
<code class="docutils literal notranslate"><span class="pre">decimal</span></code> module implements to retrieve its context from
|
||
<code class="docutils literal notranslate"><span class="pre">PyThreadState_GetDict()</span></code>. See <a class="pep reference internal" href="../pep-0550/" title="PEP 550 – Execution Context">PEP 550</a> which explains the
|
||
implementation of the cache in great detail.</p>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">Token</span></code> class is implemented as follows:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Token</span><span class="p">:</span>
|
||
|
||
<span class="n">MISSING</span> <span class="o">=</span> <span class="nb">object</span><span class="p">()</span>
|
||
|
||
<span class="k">def</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="bp">self</span><span class="o">.</span><span class="n">_used</span> <span class="o">=</span> <span class="kc">False</span>
|
||
|
||
<span class="nd">@property</span>
|
||
<span class="k">def</span> <span class="nf">var</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_var</span>
|
||
|
||
<span class="nd">@property</span>
|
||
<span class="k">def</span> <span class="nf">old_value</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_old_value</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="summary-of-the-new-apis">
|
||
<h2><a class="toc-backref" href="#summary-of-the-new-apis" role="doc-backlink">Summary of the New APIs</a></h2>
|
||
<section id="python-api">
|
||
<h3><a class="toc-backref" href="#python-api" role="doc-backlink">Python API</a></h3>
|
||
<ol class="arabic simple">
|
||
<li>A new <code class="docutils literal notranslate"><span class="pre">contextvars</span></code> module with <code class="docutils literal notranslate"><span class="pre">ContextVar</span></code>, <code class="docutils literal notranslate"><span class="pre">Context</span></code>,
|
||
and <code class="docutils literal notranslate"><span class="pre">Token</span></code> classes, and a <code class="docutils literal notranslate"><span class="pre">copy_context()</span></code> function.</li>
|
||
<li><code class="docutils literal notranslate"><span class="pre">asyncio.Loop.call_at()</span></code>, <code class="docutils literal notranslate"><span class="pre">asyncio.Loop.call_later()</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">asyncio.Loop.call_soon()</span></code>, and
|
||
<code class="docutils literal notranslate"><span class="pre">asyncio.Future.add_done_callback()</span></code> run callback functions in
|
||
the context they were called in. A new <em>context</em> keyword-only
|
||
parameter can be used to specify a custom context.</li>
|
||
<li><code class="docutils literal notranslate"><span class="pre">asyncio.Task</span></code> is modified internally to maintain its own
|
||
context.</li>
|
||
</ol>
|
||
</section>
|
||
<section id="c-api">
|
||
<h3><a class="toc-backref" href="#c-api" role="doc-backlink">C API</a></h3>
|
||
<ol class="arabic">
|
||
<li><code class="docutils literal notranslate"><span class="pre">PyObject</span> <span class="pre">*</span> <span class="pre">PyContextVar_New(char</span> <span class="pre">*name,</span> <span class="pre">PyObject</span> <span class="pre">*default)</span></code>:
|
||
create a <code class="docutils literal notranslate"><span class="pre">ContextVar</span></code> object. The <em>default</em> argument can be
|
||
<code class="docutils literal notranslate"><span class="pre">NULL</span></code>, which means that the variable has no default value.</li>
|
||
<li><code class="docutils literal notranslate"><span class="pre">int</span> <span class="pre">PyContextVar_Get(PyObject</span> <span class="pre">*,</span> <span class="pre">PyObject</span> <span class="pre">*default_value,</span> <span class="pre">PyObject</span> <span class="pre">**value)</span></code>:
|
||
return <code class="docutils literal notranslate"><span class="pre">-1</span></code> if an error occurs during the lookup, <code class="docutils literal notranslate"><span class="pre">0</span></code> otherwise.
|
||
If a value for the context variable is found, it will be set to the
|
||
<code class="docutils literal notranslate"><span class="pre">value</span></code> pointer. Otherwise, <code class="docutils literal notranslate"><span class="pre">value</span></code> will be set to
|
||
<code class="docutils literal notranslate"><span class="pre">default_value</span></code> when it is not <code class="docutils literal notranslate"><span class="pre">NULL</span></code>. If <code class="docutils literal notranslate"><span class="pre">default_value</span></code> is
|
||
<code class="docutils literal notranslate"><span class="pre">NULL</span></code>, <code class="docutils literal notranslate"><span class="pre">value</span></code> will be set to the default value of the
|
||
variable, which can be <code class="docutils literal notranslate"><span class="pre">NULL</span></code> too. <code class="docutils literal notranslate"><span class="pre">value</span></code> is always a new
|
||
reference.</li>
|
||
<li><code class="docutils literal notranslate"><span class="pre">PyObject</span> <span class="pre">*</span> <span class="pre">PyContextVar_Set(PyObject</span> <span class="pre">*,</span> <span class="pre">PyObject</span> <span class="pre">*)</span></code>:
|
||
set the value of the variable in the current context.</li>
|
||
<li><code class="docutils literal notranslate"><span class="pre">PyContextVar_Reset(PyObject</span> <span class="pre">*,</span> <span class="pre">PyObject</span> <span class="pre">*)</span></code>:
|
||
reset the value of the context variable.</li>
|
||
<li><code class="docutils literal notranslate"><span class="pre">PyObject</span> <span class="pre">*</span> <span class="pre">PyContext_New()</span></code>: create a new empty context.</li>
|
||
<li><code class="docutils literal notranslate"><span class="pre">PyObject</span> <span class="pre">*</span> <span class="pre">PyContext_Copy(PyObject</span> <span class="pre">*)</span></code>: return a shallow
|
||
copy of the passed context object.</li>
|
||
<li><code class="docutils literal notranslate"><span class="pre">PyObject</span> <span class="pre">*</span> <span class="pre">PyContext_CopyCurrent()</span></code>: get a copy of the current
|
||
context.</li>
|
||
<li><code class="docutils literal notranslate"><span class="pre">int</span> <span class="pre">PyContext_Enter(PyObject</span> <span class="pre">*)</span></code> and
|
||
<code class="docutils literal notranslate"><span class="pre">int</span> <span class="pre">PyContext_Exit(PyObject</span> <span class="pre">*)</span></code> allow to set and restore
|
||
the context for the current OS thread. It is required to always
|
||
restore the previous context:<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">PyObject</span> <span class="o">*</span><span class="n">old_ctx</span> <span class="o">=</span> <span class="n">PyContext_Copy</span><span class="p">();</span>
|
||
<span class="k">if</span> <span class="p">(</span><span class="n">old_ctx</span> <span class="o">==</span> <span class="n">NULL</span><span class="p">)</span> <span class="n">goto</span> <span class="n">error</span><span class="p">;</span>
|
||
|
||
<span class="k">if</span> <span class="p">(</span><span class="n">PyContext_Enter</span><span class="p">(</span><span class="n">new_ctx</span><span class="p">))</span> <span class="n">goto</span> <span class="n">error</span><span class="p">;</span>
|
||
|
||
<span class="o">//</span> <span class="n">run</span> <span class="n">some</span> <span class="n">code</span>
|
||
|
||
<span class="k">if</span> <span class="p">(</span><span class="n">PyContext_Exit</span><span class="p">(</span><span class="n">old_ctx</span><span class="p">))</span> <span class="n">goto</span> <span class="n">error</span><span class="p">;</span>
|
||
</pre></div>
|
||
</div>
|
||
</li>
|
||
</ol>
|
||
</section>
|
||
</section>
|
||
<section id="rejected-ideas">
|
||
<h2><a class="toc-backref" href="#rejected-ideas" role="doc-backlink">Rejected Ideas</a></h2>
|
||
<section id="replicating-threading-local-interface">
|
||
<h3><a class="toc-backref" href="#replicating-threading-local-interface" role="doc-backlink">Replicating threading.local() interface</a></h3>
|
||
<p>Please refer to <a class="pep reference internal" href="../pep-0550/" title="PEP 550 – Execution Context">PEP 550</a> where this topic is covered in detail: <a class="footnote-reference brackets" href="#id8" id="id3">[2]</a>.</p>
|
||
</section>
|
||
<section id="replacing-token-with-contextvar-unset">
|
||
<h3><a class="toc-backref" href="#replacing-token-with-contextvar-unset" role="doc-backlink">Replacing Token with ContextVar.unset()</a></h3>
|
||
<p>The Token API allows to get around having a <code class="docutils literal notranslate"><span class="pre">ContextVar.unset()</span></code>
|
||
method, which is incompatible with chained contexts design of
|
||
<a class="pep reference internal" href="../pep-0550/" title="PEP 550 – Execution Context">PEP 550</a>. Future compatibility with <a class="pep reference internal" href="../pep-0550/" title="PEP 550 – Execution Context">PEP 550</a> is desired
|
||
in case there is demand to support context variables in generators
|
||
and asynchronous generators.</p>
|
||
<p>The Token API also offers better usability: the user does not have
|
||
to special-case absence of a value. Compare:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">token</span> <span class="o">=</span> <span class="n">cv</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="n">new_value</span><span class="p">)</span>
|
||
<span class="k">try</span><span class="p">:</span>
|
||
<span class="c1"># cv.get() is new_value</span>
|
||
<span class="k">finally</span><span class="p">:</span>
|
||
<span class="n">cv</span><span class="o">.</span><span class="n">reset</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>with:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">_deleted</span> <span class="o">=</span> <span class="nb">object</span><span class="p">()</span>
|
||
<span class="n">old</span> <span class="o">=</span> <span class="n">cv</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="n">_deleted</span><span class="p">)</span>
|
||
<span class="k">try</span><span class="p">:</span>
|
||
<span class="n">cv</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="n">blah</span><span class="p">)</span>
|
||
<span class="c1"># code</span>
|
||
<span class="k">finally</span><span class="p">:</span>
|
||
<span class="k">if</span> <span class="n">old</span> <span class="ow">is</span> <span class="n">_deleted</span><span class="p">:</span>
|
||
<span class="n">cv</span><span class="o">.</span><span class="n">unset</span><span class="p">()</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">cv</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="n">old</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="having-token-reset-instead-of-contextvar-reset">
|
||
<h3><a class="toc-backref" href="#having-token-reset-instead-of-contextvar-reset" role="doc-backlink">Having Token.reset() instead of ContextVar.reset()</a></h3>
|
||
<p>Nathaniel Smith suggested to implement the <code class="docutils literal notranslate"><span class="pre">ContextVar.reset()</span></code>
|
||
method directly on the <code class="docutils literal notranslate"><span class="pre">Token</span></code> class, so instead of:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">token</span> <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
|
||
<span class="c1"># ...</span>
|
||
<span class="n">var</span><span class="o">.</span><span class="n">reset</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>we would write:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">token</span> <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
|
||
<span class="c1"># ...</span>
|
||
<span class="n">token</span><span class="o">.</span><span class="n">reset</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Having <code class="docutils literal notranslate"><span class="pre">Token.reset()</span></code> would make it impossible for a user to
|
||
attempt to reset a variable with a token object created by another
|
||
variable.</p>
|
||
<p>This proposal was rejected for the reason of <code class="docutils literal notranslate"><span class="pre">ContextVar.reset()</span></code>
|
||
being clearer to the human reader of the code which variable is
|
||
being reset.</p>
|
||
</section>
|
||
<section id="making-context-objects-picklable">
|
||
<h3><a class="toc-backref" href="#making-context-objects-picklable" role="doc-backlink">Making Context objects picklable</a></h3>
|
||
<p>Proposed by Antoine Pitrou, this could enable transparent
|
||
cross-process use of <code class="docutils literal notranslate"><span class="pre">Context</span></code> objects, so the
|
||
<a class="reference internal" href="#offloading-execution-to-other-threads">Offloading execution to other threads</a> example would work with
|
||
a <code class="docutils literal notranslate"><span class="pre">ProcessPoolExecutor</span></code> too.</p>
|
||
<p>Enabling this is problematic because of the following reasons:</p>
|
||
<ol class="arabic simple">
|
||
<li><code class="docutils literal notranslate"><span class="pre">ContextVar</span></code> objects do not have <code class="docutils literal notranslate"><span class="pre">__module__</span></code> and
|
||
<code class="docutils literal notranslate"><span class="pre">__qualname__</span></code> attributes, making straightforward pickling
|
||
of <code class="docutils literal notranslate"><span class="pre">Context</span></code> objects impossible. This is solvable by modifying
|
||
the API to either auto detect the module where a context variable
|
||
is defined, or by adding a new keyword-only “module” parameter
|
||
to <code class="docutils literal notranslate"><span class="pre">ContextVar</span></code> constructor.</li>
|
||
<li>Not all context variables refer to picklable objects. Making a
|
||
<code class="docutils literal notranslate"><span class="pre">ContextVar</span></code> picklable must be an opt-in.</li>
|
||
</ol>
|
||
<p>Given the time frame of the Python 3.7 release schedule it was decided
|
||
to defer this proposal to Python 3.8.</p>
|
||
</section>
|
||
<section id="making-context-a-mutablemapping">
|
||
<h3><a class="toc-backref" href="#making-context-a-mutablemapping" role="doc-backlink">Making Context a MutableMapping</a></h3>
|
||
<p>Making the <code class="docutils literal notranslate"><span class="pre">Context</span></code> class implement the <code class="docutils literal notranslate"><span class="pre">abc.MutableMapping</span></code>
|
||
interface would mean that it is possible to set and unset variables
|
||
using <code class="docutils literal notranslate"><span class="pre">Context[var]</span> <span class="pre">=</span> <span class="pre">value</span></code> and <code class="docutils literal notranslate"><span class="pre">del</span> <span class="pre">Context[var]</span></code> operations.</p>
|
||
<p>This proposal was deferred to Python 3.8+ because of the following:</p>
|
||
<ol class="arabic">
|
||
<li>If in Python 3.8 it is decided that generators should support
|
||
context variables (see <a class="pep reference internal" href="../pep-0550/" title="PEP 550 – Execution Context">PEP 550</a> and <a class="pep reference internal" href="../pep-0568/" title="PEP 568 – Generator-sensitivity for Context Variables">PEP 568</a>), then <code class="docutils literal notranslate"><span class="pre">Context</span></code>
|
||
would be transformed into a chain-map of context variables mappings
|
||
(as every generator would have its own mapping). That would make
|
||
mutation operations like <code class="docutils literal notranslate"><span class="pre">Context.__delitem__</span></code> confusing, as
|
||
they would operate only on the topmost mapping of the chain.</li>
|
||
<li>Having a single way of mutating the context
|
||
(<code class="docutils literal notranslate"><span class="pre">ContextVar.set()</span></code> and <code class="docutils literal notranslate"><span class="pre">ContextVar.reset()</span></code> methods) makes
|
||
the API more straightforward.<p>For example, it would be non-obvious why the below code fragment
|
||
does not work as expected:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">var</span> <span class="o">=</span> <span class="n">ContextVar</span><span class="p">(</span><span class="s1">'var'</span><span class="p">)</span>
|
||
|
||
<span class="n">ctx</span> <span class="o">=</span> <span class="n">copy_context</span><span class="p">()</span>
|
||
<span class="n">ctx</span><span class="p">[</span><span class="n">var</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'value'</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="n">ctx</span><span class="p">[</span><span class="n">var</span><span class="p">])</span> <span class="c1"># Prints 'value'</span>
|
||
|
||
<span class="nb">print</span><span class="p">(</span><span class="n">var</span><span class="o">.</span><span class="n">get</span><span class="p">())</span> <span class="c1"># Raises a LookupError</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>While the following code would work:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">ctx</span> <span class="o">=</span> <span class="n">copy_context</span><span class="p">()</span>
|
||
|
||
<span class="k">def</span> <span class="nf">func</span><span class="p">():</span>
|
||
<span class="n">ctx</span><span class="p">[</span><span class="n">var</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'value'</span>
|
||
|
||
<span class="c1"># Contrary to the previous example, this would work</span>
|
||
<span class="c1"># because 'func()' is running within 'ctx'.</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="n">ctx</span><span class="p">[</span><span class="n">var</span><span class="p">])</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="n">var</span><span class="o">.</span><span class="n">get</span><span class="p">())</span>
|
||
|
||
<span class="n">ctx</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
</li>
|
||
<li>If <code class="docutils literal notranslate"><span class="pre">Context</span></code> was mutable it would mean that context variables
|
||
could be mutated separately (or concurrently) from the code that
|
||
runs within the context. That would be similar to obtaining a
|
||
reference to a running Python frame object and modifying its
|
||
<code class="docutils literal notranslate"><span class="pre">f_locals</span></code> from another OS thread. Having one single way to
|
||
assign values to context variables makes contexts conceptually
|
||
simpler and more predictable, while keeping the door open for
|
||
future performance optimizations.</li>
|
||
</ol>
|
||
</section>
|
||
<section id="having-initial-values-for-contextvars">
|
||
<h3><a class="toc-backref" href="#having-initial-values-for-contextvars" role="doc-backlink">Having initial values for ContextVars</a></h3>
|
||
<p>Nathaniel Smith proposed to have a required <code class="docutils literal notranslate"><span class="pre">initial_value</span></code>
|
||
keyword-only argument for the <code class="docutils literal notranslate"><span class="pre">ContextVar</span></code> constructor.</p>
|
||
<p>The main argument against this proposal is that for some types
|
||
there is simply no sensible “initial value” except <code class="docutils literal notranslate"><span class="pre">None</span></code>.
|
||
E.g. consider a web framework that stores the current HTTP
|
||
request object in a context variable. With the current semantics
|
||
it is possible to create a context variable without a default value:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Framework:</span>
|
||
<span class="n">current_request</span><span class="p">:</span> <span class="n">ContextVar</span><span class="p">[</span><span class="n">Request</span><span class="p">]</span> <span class="o">=</span> \
|
||
<span class="n">ContextVar</span><span class="p">(</span><span class="s1">'current_request'</span><span class="p">)</span>
|
||
|
||
|
||
<span class="c1"># Later, while handling an HTTP request:</span>
|
||
<span class="n">request</span><span class="p">:</span> <span class="n">Request</span> <span class="o">=</span> <span class="n">current_request</span><span class="o">.</span><span class="n">get</span><span class="p">()</span>
|
||
|
||
<span class="c1"># Work with the 'request' object:</span>
|
||
<span class="k">return</span> <span class="n">request</span><span class="o">.</span><span class="n">method</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Note that in the above example there is no need to check if
|
||
<code class="docutils literal notranslate"><span class="pre">request</span></code> is <code class="docutils literal notranslate"><span class="pre">None</span></code>. It is simply expected that the framework
|
||
always sets the <code class="docutils literal notranslate"><span class="pre">current_request</span></code> variable, or it is a bug (in
|
||
which case <code class="docutils literal notranslate"><span class="pre">current_request.get()</span></code> would raise a <code class="docutils literal notranslate"><span class="pre">LookupError</span></code>).</p>
|
||
<p>If, however, we had a required initial value, we would have
|
||
to guard against <code class="docutils literal notranslate"><span class="pre">None</span></code> values explicitly:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Framework:</span>
|
||
<span class="n">current_request</span><span class="p">:</span> <span class="n">ContextVar</span><span class="p">[</span><span class="n">Optional</span><span class="p">[</span><span class="n">Request</span><span class="p">]]</span> <span class="o">=</span> \
|
||
<span class="n">ContextVar</span><span class="p">(</span><span class="s1">'current_request'</span><span class="p">,</span> <span class="n">initial_value</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
|
||
|
||
|
||
<span class="c1"># Later, while handling an HTTP request:</span>
|
||
<span class="n">request</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Request</span><span class="p">]</span> <span class="o">=</span> <span class="n">current_request</span><span class="o">.</span><span class="n">get</span><span class="p">()</span>
|
||
|
||
<span class="c1"># Check if the current request object was set:</span>
|
||
<span class="k">if</span> <span class="n">request</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||
<span class="k">raise</span> <span class="ne">RuntimeError</span>
|
||
|
||
<span class="c1"># Work with the 'request' object:</span>
|
||
<span class="k">return</span> <span class="n">request</span><span class="o">.</span><span class="n">method</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Moreover, we can loosely compare context variables to regular
|
||
Python variables and to <code class="docutils literal notranslate"><span class="pre">threading.local()</span></code> objects. Both
|
||
of them raise errors on failed lookups (<code class="docutils literal notranslate"><span class="pre">NameError</span></code> and
|
||
<code class="docutils literal notranslate"><span class="pre">AttributeError</span></code> respectively).</p>
|
||
</section>
|
||
</section>
|
||
<section id="backwards-compatibility">
|
||
<h2><a class="toc-backref" href="#backwards-compatibility" role="doc-backlink">Backwards Compatibility</a></h2>
|
||
<p>This proposal preserves 100% backwards compatibility.</p>
|
||
<p>Libraries that use <code class="docutils literal notranslate"><span class="pre">threading.local()</span></code> to store context-related
|
||
values, currently work correctly only for synchronous code. Switching
|
||
them to use the proposed API will keep their behavior for synchronous
|
||
code unmodified, but will automatically enable support for
|
||
asynchronous code.</p>
|
||
</section>
|
||
<section id="examples">
|
||
<h2><a class="toc-backref" href="#examples" role="doc-backlink">Examples</a></h2>
|
||
<section id="converting-code-that-uses-threading-local">
|
||
<h3><a class="toc-backref" href="#converting-code-that-uses-threading-local" role="doc-backlink">Converting code that uses threading.local()</a></h3>
|
||
<p>A typical code fragment that uses <code class="docutils literal notranslate"><span class="pre">threading.local()</span></code> usually
|
||
looks like the following:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">PrecisionStorage</span><span class="p">(</span><span class="n">threading</span><span class="o">.</span><span class="n">local</span><span class="p">):</span>
|
||
<span class="c1"># Subclass threading.local to specify a default value.</span>
|
||
<span class="n">value</span> <span class="o">=</span> <span class="mf">0.0</span>
|
||
|
||
<span class="n">precision</span> <span class="o">=</span> <span class="n">PrecisionStorage</span><span class="p">()</span>
|
||
|
||
<span class="c1"># To set a new precision:</span>
|
||
<span class="n">precision</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="mf">0.5</span>
|
||
|
||
<span class="c1"># To read the current precision:</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="n">precision</span><span class="o">.</span><span class="n">value</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Such code can be converted to use the <code class="docutils literal notranslate"><span class="pre">contextvars</span></code> module:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">precision</span> <span class="o">=</span> <span class="n">contextvars</span><span class="o">.</span><span class="n">ContextVar</span><span class="p">(</span><span class="s1">'precision'</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="mf">0.0</span><span class="p">)</span>
|
||
|
||
<span class="c1"># To set a new precision:</span>
|
||
<span class="n">precision</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>
|
||
|
||
<span class="c1"># To read the current precision:</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="n">precision</span><span class="o">.</span><span class="n">get</span><span class="p">())</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="offloading-execution-to-other-threads">
|
||
<h3><a class="toc-backref" href="#offloading-execution-to-other-threads" role="doc-backlink">Offloading execution to other threads</a></h3>
|
||
<p>It is possible to run code in a separate OS thread using a copy
|
||
of the current thread context:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">executor</span> <span class="o">=</span> <span class="n">ThreadPoolExecutor</span><span class="p">()</span>
|
||
<span class="n">current_context</span> <span class="o">=</span> <span class="n">contextvars</span><span class="o">.</span><span class="n">copy_context</span><span class="p">()</span>
|
||
|
||
<span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">current_context</span><span class="o">.</span><span class="n">run</span><span class="p">,</span> <span class="n">some_function</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
</section>
|
||
<section id="reference-implementation">
|
||
<h2><a class="toc-backref" href="#reference-implementation" role="doc-backlink">Reference Implementation</a></h2>
|
||
<p>The reference implementation can be found here: <a class="footnote-reference brackets" href="#id9" id="id4">[3]</a>.
|
||
See also issue 32436 <a class="footnote-reference brackets" href="#id10" id="id5">[4]</a>.</p>
|
||
</section>
|
||
<section id="acceptance">
|
||
<h2><a class="toc-backref" href="#acceptance" role="doc-backlink">Acceptance</a></h2>
|
||
<p><a class="pep reference internal" href="../pep-0567/" title="PEP 567 – Context Variables">PEP 567</a> was accepted by Guido on Monday, January 22, 2018 <a class="footnote-reference brackets" href="#id11" id="id6">[5]</a>.
|
||
The reference implementation was merged on the same day.</p>
|
||
</section>
|
||
<section id="references">
|
||
<h2><a class="toc-backref" href="#references" role="doc-backlink">References</a></h2>
|
||
<aside class="footnote-list brackets">
|
||
<aside class="footnote brackets" id="id7" role="doc-footnote">
|
||
<dt class="label" id="id7">[<a href="#id2">1</a>]</dt>
|
||
<dd><a class="pep reference internal" href="../pep-0550/#appendix-hamt-performance-analysis" title="PEP 550 – Execution Context § Appendix: HAMT Performance Analysis">PEP 550</a></aside>
|
||
<aside class="footnote brackets" id="id8" role="doc-footnote">
|
||
<dt class="label" id="id8">[<a href="#id3">2</a>]</dt>
|
||
<dd><a class="pep reference internal" href="../pep-0550/#replication-of-threading-local-interface" title="PEP 550 – Execution Context § Replication of threading.local() interface">PEP 550</a></aside>
|
||
<aside class="footnote brackets" id="id9" role="doc-footnote">
|
||
<dt class="label" id="id9">[<a href="#id4">3</a>]</dt>
|
||
<dd><a class="reference external" href="https://github.com/python/cpython/pull/5027">https://github.com/python/cpython/pull/5027</a></aside>
|
||
<aside class="footnote brackets" id="id10" role="doc-footnote">
|
||
<dt class="label" id="id10">[<a href="#id5">4</a>]</dt>
|
||
<dd><a class="reference external" href="https://bugs.python.org/issue32436">https://bugs.python.org/issue32436</a></aside>
|
||
<aside class="footnote brackets" id="id11" role="doc-footnote">
|
||
<dt class="label" id="id11">[<a href="#id6">5</a>]</dt>
|
||
<dd><a class="reference external" href="https://mail.python.org/pipermail/python-dev/2018-January/151878.html">https://mail.python.org/pipermail/python-dev/2018-January/151878.html</a></aside>
|
||
<aside class="footnote brackets" id="id12" role="doc-footnote">
|
||
<dt class="label" id="id12">[<a href="#id1">6</a>]</dt>
|
||
<dd><a class="reference external" href="https://bugs.python.org/issue34762">https://bugs.python.org/issue34762</a></aside>
|
||
</aside>
|
||
</section>
|
||
<section id="acknowledgments">
|
||
<h2><a class="toc-backref" href="#acknowledgments" role="doc-backlink">Acknowledgments</a></h2>
|
||
<p>I thank Guido van Rossum, Nathaniel Smith, Victor Stinner,
|
||
Elvis Pranskevichus, Alyssa Coghlan, Antoine Pitrou, INADA Naoki,
|
||
Paul Moore, Eric Snow, Greg Ewing, and many others for their feedback,
|
||
ideas, edits, criticism, code reviews, and discussions around
|
||
this PEP.</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-0567.rst">https://github.com/python/peps/blob/main/peps/pep-0567.rst</a></p>
|
||
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0567.rst">2023-10-11 12:05:51 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="#api-design-and-implementation-revisions">API Design and Implementation Revisions</a></li>
|
||
<li><a class="reference internal" href="#rationale">Rationale</a></li>
|
||
<li><a class="reference internal" href="#introduction">Introduction</a></li>
|
||
<li><a class="reference internal" href="#specification">Specification</a><ul>
|
||
<li><a class="reference internal" href="#contextvars-contextvar">contextvars.ContextVar</a></li>
|
||
<li><a class="reference internal" href="#contextvars-token">contextvars.Token</a></li>
|
||
<li><a class="reference internal" href="#contextvars-context">contextvars.Context</a></li>
|
||
<li><a class="reference internal" href="#asyncio">asyncio</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#implementation">Implementation</a></li>
|
||
<li><a class="reference internal" href="#summary-of-the-new-apis">Summary of the New APIs</a><ul>
|
||
<li><a class="reference internal" href="#python-api">Python API</a></li>
|
||
<li><a class="reference internal" href="#c-api">C API</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a><ul>
|
||
<li><a class="reference internal" href="#replicating-threading-local-interface">Replicating threading.local() interface</a></li>
|
||
<li><a class="reference internal" href="#replacing-token-with-contextvar-unset">Replacing Token with ContextVar.unset()</a></li>
|
||
<li><a class="reference internal" href="#having-token-reset-instead-of-contextvar-reset">Having Token.reset() instead of ContextVar.reset()</a></li>
|
||
<li><a class="reference internal" href="#making-context-objects-picklable">Making Context objects picklable</a></li>
|
||
<li><a class="reference internal" href="#making-context-a-mutablemapping">Making Context a MutableMapping</a></li>
|
||
<li><a class="reference internal" href="#having-initial-values-for-contextvars">Having initial values for ContextVars</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></li>
|
||
<li><a class="reference internal" href="#examples">Examples</a><ul>
|
||
<li><a class="reference internal" href="#converting-code-that-uses-threading-local">Converting code that uses threading.local()</a></li>
|
||
<li><a class="reference internal" href="#offloading-execution-to-other-threads">Offloading execution to other threads</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
|
||
<li><a class="reference internal" href="#acceptance">Acceptance</a></li>
|
||
<li><a class="reference internal" href="#references">References</a></li>
|
||
<li><a class="reference internal" href="#acknowledgments">Acknowledgments</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-0567.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> |