547 lines
47 KiB
HTML
547 lines
47 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 667 – Consistent views of namespaces | peps.python.org</title>
|
||
<link rel="shortcut icon" href="../_static/py.png">
|
||
<link rel="canonical" href="https://peps.python.org/pep-0667/">
|
||
<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 667 – Consistent views of namespaces | peps.python.org'>
|
||
<meta property="og:description" content="In early versions of Python all namespaces, whether in functions, classes or modules, were all implemented the same way: as a dictionary.">
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:url" content="https://peps.python.org/pep-0667/">
|
||
<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="In early versions of Python all namespaces, whether in functions, classes or modules, were all implemented the same way: as a dictionary.">
|
||
<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 667</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 667 – Consistent views of namespaces</h1>
|
||
<dl class="rfc2822 field-list simple">
|
||
<dt class="field-odd">Author<span class="colon">:</span></dt>
|
||
<dd class="field-odd">Mark Shannon <mark at hotpy.org>,
|
||
Tian Gao <gaogaotiantian at hotmail.com></dd>
|
||
<dt class="field-even">Discussions-To<span class="colon">:</span></dt>
|
||
<dd class="field-even"><a class="reference external" href="https://discuss.python.org/t/46631">Discourse thread</a></dd>
|
||
<dt class="field-odd">Status<span class="colon">:</span></dt>
|
||
<dd class="field-odd"><abbr title="Normative proposal accepted for implementation">Accepted</abbr></dd>
|
||
<dt class="field-even">Type<span class="colon">:</span></dt>
|
||
<dd class="field-even"><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-odd">Created<span class="colon">:</span></dt>
|
||
<dd class="field-odd">30-Jul-2021</dd>
|
||
<dt class="field-even">Python-Version<span class="colon">:</span></dt>
|
||
<dd class="field-even">3.13</dd>
|
||
<dt class="field-odd">Post-History<span class="colon">:</span></dt>
|
||
<dd class="field-odd">20-Aug-2021</dd>
|
||
<dt class="field-even">Resolution<span class="colon">:</span></dt>
|
||
<dd class="field-even"><a class="reference external" href="https://discuss.python.org/t/pep-667-consistent-views-of-namespaces/46631/25">Discourse message</a></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="#motivation">Motivation</a></li>
|
||
<li><a class="reference internal" href="#rationale">Rationale</a></li>
|
||
<li><a class="reference internal" href="#specification">Specification</a><ul>
|
||
<li><a class="reference internal" href="#python">Python</a></li>
|
||
<li><a class="reference internal" href="#c-api">C-API</a><ul>
|
||
<li><a class="reference internal" href="#extensions-to-the-api">Extensions to the API</a></li>
|
||
<li><a class="reference internal" href="#changes-to-existing-apis">Changes to existing APIs</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#behavior-of-f-locals-for-optimized-functions">Behavior of f_locals for optimized functions</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a><ul>
|
||
<li><a class="reference internal" href="#id1">Python</a></li>
|
||
<li><a class="reference internal" href="#id2">C-API</a><ul>
|
||
<li><a class="reference internal" href="#pyeval-getlocals">PyEval_GetLocals</a></li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#implementation">Implementation</a><ul>
|
||
<li><a class="reference internal" href="#id3">C API</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#impact-on-pep-709-inlined-comprehensions">Impact on PEP 709 inlined comprehensions</a></li>
|
||
<li><a class="reference internal" href="#comparison-with-pep-558">Comparison with PEP 558</a></li>
|
||
<li><a class="reference internal" href="#id4">Implementation</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>In early versions of Python all namespaces, whether in functions,
|
||
classes or modules, were all implemented the same way: as a dictionary.</p>
|
||
<p>For performance reasons, the implementation of function namespaces was
|
||
changed. Unfortunately this meant that accessing these namespaces through
|
||
<code class="docutils literal notranslate"><span class="pre">locals()</span></code> and <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> ceased to be consistent and some
|
||
odd bugs crept in over the years as threads, generators and coroutines
|
||
were added.</p>
|
||
<p>This PEP proposes making these namespaces consistent once more.
|
||
Modifications to <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> will always be visible in
|
||
the underlying variables. Modifications to local variables will
|
||
immediately be visible in <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code>, and they will be
|
||
consistent regardless of threading or coroutines.</p>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">locals()</span></code> function will act the same as it does now for class
|
||
and modules scopes. For function scopes it will return an instantaneous
|
||
snapshot of the underlying <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code>.</p>
|
||
</section>
|
||
<section id="motivation">
|
||
<h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2>
|
||
<p>The current implementation of <code class="docutils literal notranslate"><span class="pre">locals()</span></code> and <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> is slow,
|
||
inconsistent and buggy.
|
||
We want to make it faster, consistent, and most importantly fix the bugs.</p>
|
||
<p>For example:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">C</span><span class="p">:</span>
|
||
<span class="n">x</span> <span class="o">=</span> <span class="mi">1</span>
|
||
<span class="n">sys</span><span class="o">.</span><span class="n">_getframe</span><span class="p">()</span><span class="o">.</span><span class="n">f_locals</span><span class="p">[</span><span class="s1">'x'</span><span class="p">]</span> <span class="o">=</span> <span class="mi">2</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>prints <code class="docutils literal notranslate"><span class="pre">2</span></code></p>
|
||
<p>but:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">f</span><span class="p">():</span>
|
||
<span class="n">x</span> <span class="o">=</span> <span class="mi">1</span>
|
||
<span class="n">sys</span><span class="o">.</span><span class="n">_getframe</span><span class="p">()</span><span class="o">.</span><span class="n">f_locals</span><span class="p">[</span><span class="s1">'x'</span><span class="p">]</span> <span class="o">=</span> <span class="mi">2</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
|
||
<span class="n">f</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>prints <code class="docutils literal notranslate"><span class="pre">1</span></code></p>
|
||
<p>This is inconsistent, and confusing.
|
||
With this PEP both examples would print <code class="docutils literal notranslate"><span class="pre">2</span></code>.</p>
|
||
<p>Worse than that, the current behavior can result in strange <a class="reference external" href="https://github.com/python/cpython/issues/74929">bugs</a>.</p>
|
||
<p>There are no compensating advantages for the current behavior;
|
||
it is unreliable and slow.</p>
|
||
</section>
|
||
<section id="rationale">
|
||
<h2><a class="toc-backref" href="#rationale" role="doc-backlink">Rationale</a></h2>
|
||
<p>The current implementation of <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> returns a dictionary
|
||
that is created on the fly from the array of local variables.
|
||
This can result in the array and dictionary getting out of sync with
|
||
each other. Writes to the <code class="docutils literal notranslate"><span class="pre">f_locals</span></code> may not show up as
|
||
modifications to local variables. Writes to local variables can get lost.</p>
|
||
<p>By making <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> return a view on the
|
||
underlying frame, these problems go away. <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> is always in
|
||
sync with the frame because it is a view of it, not a copy of it.</p>
|
||
</section>
|
||
<section id="specification">
|
||
<h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2>
|
||
<section id="python">
|
||
<h3><a class="toc-backref" href="#python" role="doc-backlink">Python</a></h3>
|
||
<p><code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> will return a view object on the frame that
|
||
implements the <code class="docutils literal notranslate"><span class="pre">collections.abc.Mapping</span></code> interface.</p>
|
||
<p>For module and class scopes <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> will be a dictionary,
|
||
for function scopes it will be a custom class.</p>
|
||
<p><code class="docutils literal notranslate"><span class="pre">locals()</span></code> will be defined as:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">locals</span><span class="p">():</span>
|
||
<span class="n">frame</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">_getframe</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
|
||
<span class="n">f_locals</span> <span class="o">=</span> <span class="n">frame</span><span class="o">.</span><span class="n">f_locals</span>
|
||
<span class="k">if</span> <span class="n">frame</span><span class="o">.</span><span class="n">is_function</span><span class="p">():</span>
|
||
<span class="n">f_locals</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="n">f_locals</span><span class="p">)</span>
|
||
<span class="k">return</span> <span class="n">f_locals</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>All writes to the <code class="docutils literal notranslate"><span class="pre">f_locals</span></code> mapping will be immediately visible
|
||
in the underlying variables. All changes to the underlying variables
|
||
will be immediately visible in the mapping. The <code class="docutils literal notranslate"><span class="pre">f_locals</span></code> object will
|
||
be a full mapping, and can have arbitrary key-value pairs added to it.</p>
|
||
<p>For example:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">l</span><span class="p">():</span>
|
||
<span class="s2">"Get the locals of caller"</span>
|
||
<span class="k">return</span> <span class="n">sys</span><span class="o">.</span><span class="n">_getframe</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="n">f_locals</span>
|
||
|
||
<span class="k">def</span> <span class="nf">test</span><span class="p">():</span>
|
||
<span class="k">if</span> <span class="mi">0</span><span class="p">:</span> <span class="n">y</span> <span class="o">=</span> <span class="mi">1</span> <span class="c1"># Make 'y' a local variable</span>
|
||
<span class="n">x</span> <span class="o">=</span> <span class="mi">1</span>
|
||
<span class="n">l</span><span class="p">()[</span><span class="s1">'x'</span><span class="p">]</span> <span class="o">=</span> <span class="mi">2</span>
|
||
<span class="n">l</span><span class="p">()[</span><span class="s1">'y'</span><span class="p">]</span> <span class="o">=</span> <span class="mi">4</span>
|
||
<span class="n">l</span><span class="p">()[</span><span class="s1">'z'</span><span class="p">]</span> <span class="o">=</span> <span class="mi">5</span>
|
||
<span class="n">y</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="nb">locals</span><span class="p">(),</span> <span class="n">x</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="docutils literal notranslate"><span class="pre">test()</span></code> will print <code class="docutils literal notranslate"><span class="pre">{'x':</span> <span class="pre">2,</span> <span class="pre">'y':</span> <span class="pre">4,</span> <span class="pre">'z':</span> <span class="pre">5}</span> <span class="pre">2</span></code>.</p>
|
||
<p>In Python 3.10, the above will fail with an <code class="docutils literal notranslate"><span class="pre">UnboundLocalError</span></code>,
|
||
as the definition of <code class="docutils literal notranslate"><span class="pre">y</span></code> by <code class="docutils literal notranslate"><span class="pre">l()['y']</span> <span class="pre">=</span> <span class="pre">4</span></code> is lost.</p>
|
||
<p>If the second-to-last line were changed from <code class="docutils literal notranslate"><span class="pre">y</span></code> to <code class="docutils literal notranslate"><span class="pre">z</span></code>, this would be a
|
||
<code class="docutils literal notranslate"><span class="pre">NameError</span></code>, as it is today. Keys added to <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> that are not
|
||
lexically local variables remain visible in <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code>, but do not
|
||
dynamically become local variables.</p>
|
||
</section>
|
||
<section id="c-api">
|
||
<h3><a class="toc-backref" href="#c-api" role="doc-backlink">C-API</a></h3>
|
||
<section id="extensions-to-the-api">
|
||
<h4><a class="toc-backref" href="#extensions-to-the-api" role="doc-backlink">Extensions to the API</a></h4>
|
||
<p>Three new C-API functions will be added:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">PyObject</span> <span class="o">*</span><span class="n">PyEval_GetFrameLocals</span><span class="p">(</span><span class="n">void</span><span class="p">)</span>
|
||
<span class="n">PyObject</span> <span class="o">*</span><span class="n">PyEval_GetFrameGlobals</span><span class="p">(</span><span class="n">void</span><span class="p">)</span>
|
||
<span class="n">PyObject</span> <span class="o">*</span><span class="n">PyEval_GetFrameBuiltins</span><span class="p">(</span><span class="n">void</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="docutils literal notranslate"><span class="pre">PyEval_GetFrameLocals()</span></code> is equivalent to: <code class="docutils literal notranslate"><span class="pre">locals()</span></code>.
|
||
<code class="docutils literal notranslate"><span class="pre">PyEval_GetFrameGlobals()</span></code> is equivalent to: <code class="docutils literal notranslate"><span class="pre">globals()</span></code>.</p>
|
||
<p>All these functions will return a new reference.</p>
|
||
</section>
|
||
<section id="changes-to-existing-apis">
|
||
<h4><a class="toc-backref" href="#changes-to-existing-apis" role="doc-backlink">Changes to existing APIs</a></h4>
|
||
<p><code class="docutils literal notranslate"><span class="pre">PyFrame_GetLocals(f)</span></code> is equivalent to <code class="docutils literal notranslate"><span class="pre">f.f_locals</span></code>, and hence its return value
|
||
will change as described above for accessing <code class="docutils literal notranslate"><span class="pre">f.f_locals</span></code>.</p>
|
||
<p>The following C-API functions will be deprecated, as they return borrowed references:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">PyEval_GetLocals</span><span class="p">()</span>
|
||
<span class="n">PyEval_GetGlobals</span><span class="p">()</span>
|
||
<span class="n">PyEval_GetBuiltins</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The following functions should be used instead:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">PyEval_GetFrameLocals</span><span class="p">()</span>
|
||
<span class="n">PyEval_GetFrameGlobals</span><span class="p">()</span>
|
||
<span class="n">PyEval_GetFrameBuiltins</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>which return new references.</p>
|
||
<p>The semantics of <code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals()</span></code> is changed as it now returns a
|
||
proxy for the frame locals in optimized frames, not a dictionary.</p>
|
||
<p>The following three functions will become no-ops, and will be deprecated:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">PyFrame_FastToLocalsWithError</span><span class="p">()</span>
|
||
<span class="n">PyFrame_FastToLocals</span><span class="p">()</span>
|
||
<span class="n">PyFrame_LocalsToFast</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
</section>
|
||
<section id="behavior-of-f-locals-for-optimized-functions">
|
||
<h3><a class="toc-backref" href="#behavior-of-f-locals-for-optimized-functions" role="doc-backlink">Behavior of f_locals for optimized functions</a></h3>
|
||
<p>Although <code class="docutils literal notranslate"><span class="pre">f.f_locals</span></code> behaves as if it were the namespace of the function,
|
||
there will be some observable differences.
|
||
For example, <code class="docutils literal notranslate"><span class="pre">f.f_locals</span> <span class="pre">is</span> <span class="pre">f.f_locals</span></code> may be <code class="docutils literal notranslate"><span class="pre">False</span></code>.</p>
|
||
<p>However <code class="docutils literal notranslate"><span class="pre">f.f_locals</span> <span class="pre">==</span> <span class="pre">f.f_locals</span></code> will be <code class="docutils literal notranslate"><span class="pre">True</span></code>, and
|
||
all changes to the underlying variables, by any means, will always be visible.</p>
|
||
</section>
|
||
</section>
|
||
<section id="backwards-compatibility">
|
||
<h2><a class="toc-backref" href="#backwards-compatibility" role="doc-backlink">Backwards Compatibility</a></h2>
|
||
<section id="id1">
|
||
<h3><a class="toc-backref" href="#id1" role="doc-backlink">Python</a></h3>
|
||
<p>The current implementation has many corner cases and oddities.
|
||
Code that works around those may need to be changed.
|
||
Code that uses <code class="docutils literal notranslate"><span class="pre">locals()</span></code> for simple templating, or print debugging,
|
||
will continue to work correctly. Debuggers and other tools that use
|
||
<code class="docutils literal notranslate"><span class="pre">f_locals</span></code> to modify local variables, will now work correctly,
|
||
even in the presence of threaded code, coroutines and generators.</p>
|
||
</section>
|
||
<section id="id2">
|
||
<h3><a class="toc-backref" href="#id2" role="doc-backlink">C-API</a></h3>
|
||
<section id="pyeval-getlocals">
|
||
<h4><a class="toc-backref" href="#pyeval-getlocals" role="doc-backlink">PyEval_GetLocals</a></h4>
|
||
<p>Because <code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals()</span></code> returns a borrowed reference, it requires
|
||
the proxy mapping to be cached on the frame, extending its lifetime and
|
||
creating a cycle. <code class="docutils literal notranslate"><span class="pre">PyEval_GetFrameLocals()</span></code> should be used instead.</p>
|
||
<p>This code:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nb">locals</span> <span class="o">=</span> <span class="n">PyEval_GetLocals</span><span class="p">();</span>
|
||
<span class="k">if</span> <span class="p">(</span><span class="nb">locals</span> <span class="o">==</span> <span class="n">NULL</span><span class="p">)</span> <span class="p">{</span>
|
||
<span class="n">goto</span> <span class="n">error_handler</span><span class="p">;</span>
|
||
<span class="p">}</span>
|
||
<span class="n">Py_INCREF</span><span class="p">(</span><span class="nb">locals</span><span class="p">);</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>should be replaced with:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nb">locals</span> <span class="o">=</span> <span class="n">PyEval_GetFrameLocals</span><span class="p">();</span>
|
||
<span class="k">if</span> <span class="p">(</span><span class="nb">locals</span> <span class="o">==</span> <span class="n">NULL</span><span class="p">)</span> <span class="p">{</span>
|
||
<span class="n">goto</span> <span class="n">error_handler</span><span class="p">;</span>
|
||
<span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
</section>
|
||
</section>
|
||
<section id="implementation">
|
||
<h2><a class="toc-backref" href="#implementation" role="doc-backlink">Implementation</a></h2>
|
||
<p>Each read of <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> will create a new proxy object that gives
|
||
the appearance of being the mapping of local (including cell and free)
|
||
variable names to the values of those local variables.</p>
|
||
<p>A possible implementation is sketched out below.
|
||
All attributes that start with an underscore are invisible and
|
||
cannot be accessed directly.
|
||
They serve only to illustrate the proposed design.</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">NULL</span><span class="p">:</span> <span class="n">Object</span> <span class="c1"># NULL is a singleton representing the absence of a value.</span>
|
||
|
||
<span class="k">class</span> <span class="nc">CodeType</span><span class="p">:</span>
|
||
|
||
<span class="n">_name_to_offset_mapping_impl</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">|</span> <span class="n">NULL</span>
|
||
<span class="n">_cells</span><span class="p">:</span> <span class="nb">frozenset</span> <span class="c1"># Set of indexes of cell and free variables</span>
|
||
<span class="o">...</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="o">...</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">_name_to_offset_mapping_impl</span> <span class="o">=</span> <span class="n">NULL</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">_variable_names</span> <span class="o">=</span> <span class="n">deduplicate</span><span class="p">(</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">co_varnames</span> <span class="o">+</span> <span class="bp">self</span><span class="o">.</span><span class="n">co_cellvars</span> <span class="o">+</span> <span class="bp">self</span><span class="o">.</span><span class="n">co_freevars</span>
|
||
<span class="p">)</span>
|
||
<span class="o">...</span>
|
||
|
||
<span class="nd">@property</span>
|
||
<span class="k">def</span> <span class="nf">_name_to_offset_mapping</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="s2">"Mapping of names to offsets in local variable array."</span>
|
||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_name_to_offset_mapping_impl</span> <span class="ow">is</span> <span class="n">NULL</span><span class="p">:</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">_name_to_offset_mapping_impl</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="n">name</span><span class="p">:</span> <span class="n">index</span> <span class="k">for</span> <span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_variable_names</span><span class="p">)</span>
|
||
<span class="p">}</span>
|
||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_name_to_offset_mapping_impl</span>
|
||
|
||
<span class="k">class</span> <span class="nc">FrameType</span><span class="p">:</span>
|
||
|
||
<span class="n">_locals</span> <span class="p">:</span> <span class="n">array</span><span class="p">[</span><span class="n">Object</span><span class="p">]</span> <span class="c1"># The values of the local variables, items may be NULL.</span>
|
||
<span class="n">_extra_locals</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">|</span> <span class="n">NULL</span> <span class="c1"># Dictionary for storing extra locals not in _locals.</span>
|
||
<span class="n">_locals_cache</span><span class="p">:</span> <span class="n">FrameLocalsProxy</span> <span class="o">|</span> <span class="n">NULL</span> <span class="c1"># required to support PyEval_GetLocals()</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="o">...</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">_extra_locals</span> <span class="o">=</span> <span class="n">NULL</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">_locals_cache</span> <span class="o">=</span> <span class="n">NULL</span>
|
||
<span class="o">...</span>
|
||
|
||
<span class="nd">@property</span>
|
||
<span class="k">def</span> <span class="nf">f_locals</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="n">FrameLocalsProxy</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
|
||
|
||
<span class="k">class</span> <span class="nc">FrameLocalsProxy</span><span class="p">:</span>
|
||
<span class="s2">"Implements collections.MutableMapping."</span>
|
||
|
||
<span class="vm">__slots__</span> <span class="o">=</span> <span class="p">(</span><span class="s2">"_frame"</span><span class="p">,</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">frame</span><span class="p">:</span><span class="n">FrameType</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">_frame</span> <span class="o">=</span> <span class="n">frame</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">name</span><span class="p">):</span>
|
||
<span class="n">f</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_frame</span>
|
||
<span class="n">co</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">f_code</span>
|
||
<span class="k">if</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">co</span><span class="o">.</span><span class="n">_name_to_offset_mapping</span><span class="p">:</span>
|
||
<span class="n">index</span> <span class="o">=</span> <span class="n">co</span><span class="o">.</span><span class="n">_name_to_offset_mapping</span><span class="p">[</span><span class="n">name</span><span class="p">]</span>
|
||
<span class="n">val</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">_locals</span><span class="p">[</span><span class="n">index</span><span class="p">]</span>
|
||
<span class="k">if</span> <span class="n">val</span> <span class="ow">is</span> <span class="n">NULL</span><span class="p">:</span>
|
||
<span class="k">raise</span> <span class="ne">KeyError</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
|
||
<span class="k">if</span> <span class="n">index</span> <span class="ow">in</span> <span class="n">co</span><span class="o">.</span><span class="n">_cells</span>
|
||
<span class="n">val</span> <span class="o">=</span> <span class="n">val</span><span class="o">.</span><span class="n">cell_contents</span>
|
||
<span class="k">if</span> <span class="n">val</span> <span class="ow">is</span> <span class="n">NULL</span><span class="p">:</span>
|
||
<span class="k">raise</span> <span class="ne">KeyError</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
|
||
<span class="k">return</span> <span class="n">val</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="k">if</span> <span class="n">f</span><span class="o">.</span><span class="n">_extra_locals</span> <span class="ow">is</span> <span class="n">NULL</span><span class="p">:</span>
|
||
<span class="k">raise</span> <span class="ne">KeyError</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
|
||
<span class="k">return</span> <span class="n">f</span><span class="o">.</span><span class="n">_extra_locals</span><span class="p">[</span><span class="n">name</span><span class="p">]</span>
|
||
|
||
<span class="k">def</span> <span class="fm">__setitem__</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="n">value</span><span class="p">):</span>
|
||
<span class="n">f</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_frame</span>
|
||
<span class="n">co</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">f_code</span>
|
||
<span class="k">if</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">co</span><span class="o">.</span><span class="n">_name_to_offset_mapping</span><span class="p">:</span>
|
||
<span class="n">index</span> <span class="o">=</span> <span class="n">co</span><span class="o">.</span><span class="n">_name_to_offset_mapping</span><span class="p">[</span><span class="n">name</span><span class="p">]</span>
|
||
<span class="n">kind</span> <span class="o">=</span> <span class="n">co</span><span class="o">.</span><span class="n">_local_kinds</span><span class="p">[</span><span class="n">index</span><span class="p">]</span>
|
||
<span class="k">if</span> <span class="n">index</span> <span class="ow">in</span> <span class="n">co</span><span class="o">.</span><span class="n">_cells</span>
|
||
<span class="n">cell</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">_locals</span><span class="p">[</span><span class="n">index</span><span class="p">]</span>
|
||
<span class="n">cell</span><span class="o">.</span><span class="n">cell_contents</span> <span class="o">=</span> <span class="n">val</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">f</span><span class="o">.</span><span class="n">_locals</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="o">=</span> <span class="n">val</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="k">if</span> <span class="n">f</span><span class="o">.</span><span class="n">_extra_locals</span> <span class="ow">is</span> <span class="n">NULL</span><span class="p">:</span>
|
||
<span class="n">f</span><span class="o">.</span><span class="n">_extra_locals</span> <span class="o">=</span> <span class="p">{}</span>
|
||
<span class="n">f</span><span class="o">.</span><span class="n">_extra_locals</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">val</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="n">f</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_frame</span>
|
||
<span class="n">co</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">f_code</span>
|
||
<span class="k">yield from</span> <span class="nb">iter</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">_extra_locals</span><span class="p">)</span>
|
||
<span class="k">for</span> <span class="n">index</span><span class="p">,</span> <span class="n">name</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">co</span><span class="o">.</span><span class="n">_variable_names</span><span class="p">):</span>
|
||
<span class="n">val</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">_locals</span><span class="p">[</span><span class="n">index</span><span class="p">]</span>
|
||
<span class="k">if</span> <span class="n">val</span> <span class="ow">is</span> <span class="n">NULL</span><span class="p">:</span>
|
||
<span class="k">continue</span>
|
||
<span class="k">if</span> <span class="n">index</span> <span class="ow">in</span> <span class="n">co</span><span class="o">.</span><span class="n">_cells</span><span class="p">:</span>
|
||
<span class="n">val</span> <span class="o">=</span> <span class="n">val</span><span class="o">.</span><span class="n">cell_contents</span>
|
||
<span class="k">if</span> <span class="n">val</span> <span class="ow">is</span> <span class="n">NULL</span><span class="p">:</span>
|
||
<span class="k">continue</span>
|
||
<span class="k">yield</span> <span class="n">name</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">item</span><span class="p">):</span>
|
||
<span class="n">f</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_frame</span>
|
||
<span class="k">if</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">f</span><span class="o">.</span><span class="n">_extra_locals</span><span class="p">:</span>
|
||
<span class="k">return</span> <span class="kc">True</span>
|
||
<span class="k">return</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">co</span><span class="o">.</span><span class="n">_variable_names</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="n">f</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_frame</span>
|
||
<span class="n">co</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">f_code</span>
|
||
<span class="n">res</span> <span class="o">=</span> <span class="mi">0</span>
|
||
<span class="k">for</span> <span class="n">index</span><span class="p">,</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">co</span><span class="o">.</span><span class="n">_variable_names</span><span class="p">):</span>
|
||
<span class="n">val</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">_locals</span><span class="p">[</span><span class="n">index</span><span class="p">]</span>
|
||
<span class="k">if</span> <span class="n">val</span> <span class="ow">is</span> <span class="n">NULL</span><span class="p">:</span>
|
||
<span class="k">continue</span>
|
||
<span class="k">if</span> <span class="n">index</span> <span class="ow">in</span> <span class="n">co</span><span class="o">.</span><span class="n">_cells</span><span class="p">:</span>
|
||
<span class="k">if</span> <span class="n">val</span><span class="o">.</span><span class="n">cell_contents</span> <span class="ow">is</span> <span class="n">NULL</span><span class="p">:</span>
|
||
<span class="k">continue</span>
|
||
<span class="n">res</span> <span class="o">+=</span> <span class="mi">1</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">_extra_locals</span><span class="p">)</span> <span class="o">+</span> <span class="n">res</span>
|
||
</pre></div>
|
||
</div>
|
||
<section id="id3">
|
||
<h3><a class="toc-backref" href="#id3" role="doc-backlink">C API</a></h3>
|
||
<p><code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals()</span></code> will be implemented roughly as follows:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">PyObject</span> <span class="o">*</span><span class="n">PyEval_GetLocals</span><span class="p">(</span><span class="n">void</span><span class="p">)</span> <span class="p">{</span>
|
||
<span class="n">PyFrameObject</span> <span class="o">*</span> <span class="o">=</span> <span class="o">...</span><span class="p">;</span> <span class="o">//</span> <span class="n">Get</span> <span class="n">the</span> <span class="n">current</span> <span class="n">frame</span><span class="o">.</span>
|
||
<span class="k">if</span> <span class="p">(</span><span class="n">frame</span><span class="o">-></span><span class="n">_locals_cache</span> <span class="o">==</span> <span class="n">NULL</span><span class="p">)</span> <span class="p">{</span>
|
||
<span class="n">frame</span><span class="o">-></span><span class="n">_locals_cache</span> <span class="o">=</span> <span class="n">PyEval_GetFrameLocals</span><span class="p">();</span>
|
||
<span class="p">}</span>
|
||
<span class="k">return</span> <span class="n">frame</span><span class="o">-></span><span class="n">_locals_cache</span><span class="p">;</span>
|
||
<span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>As with all functions that return a borrowed reference, care must be taken to
|
||
ensure that the reference is not used beyond the lifetime of the object.</p>
|
||
</section>
|
||
</section>
|
||
<section id="impact-on-pep-709-inlined-comprehensions">
|
||
<h2><a class="toc-backref" href="#impact-on-pep-709-inlined-comprehensions" role="doc-backlink">Impact on PEP 709 inlined comprehensions</a></h2>
|
||
<p>For inlined comprehensions within a function, <code class="docutils literal notranslate"><span class="pre">locals()</span></code> currently behaves the
|
||
same inside or outside of the comprehension, and this will not change. The
|
||
behavior of <code class="docutils literal notranslate"><span class="pre">locals()</span></code> inside functions will generally change as specified in
|
||
the rest of this PEP.</p>
|
||
<p>For inlined comprehensions at module or class scope, currently calling
|
||
<code class="docutils literal notranslate"><span class="pre">locals()</span></code> within the inlined comprehension returns a new dictionary for each
|
||
call. This PEP will make <code class="docutils literal notranslate"><span class="pre">locals()</span></code> within a function also always return a new
|
||
dictionary for each call, improving consistency; class or module scope inlined
|
||
comprehensions will appear to behave as if the inlined comprehension is still a
|
||
distinct function.</p>
|
||
</section>
|
||
<section id="comparison-with-pep-558">
|
||
<h2><a class="toc-backref" href="#comparison-with-pep-558" role="doc-backlink">Comparison with PEP 558</a></h2>
|
||
<p>This PEP and <a class="pep reference internal" href="../pep-0558/" title="PEP 558 – Defined semantics for locals()">PEP 558</a> share a common goal:
|
||
to make the semantics of <code class="docutils literal notranslate"><span class="pre">locals()</span></code> and <code class="docutils literal notranslate"><span class="pre">frame.f_locals()</span></code>
|
||
intelligible, and their operation reliable.</p>
|
||
<p>The key difference between this PEP and <a class="pep reference internal" href="../pep-0558/" title="PEP 558 – Defined semantics for locals()">PEP 558</a> is that
|
||
<a class="pep reference internal" href="../pep-0558/" title="PEP 558 – Defined semantics for locals()">PEP 558</a> keeps an internal copy of the local variables,
|
||
whereas this PEP does not.</p>
|
||
<p><a class="pep reference internal" href="../pep-0558/" title="PEP 558 – Defined semantics for locals()">PEP 558</a> does not specify exactly when the internal copy is
|
||
updated, making the behavior of <a class="pep reference internal" href="../pep-0558/" title="PEP 558 – Defined semantics for locals()">PEP 558</a> impossible to reason about.</p>
|
||
</section>
|
||
<section id="id4">
|
||
<h2><a class="toc-backref" href="#id4" role="doc-backlink">Implementation</a></h2>
|
||
<p>The implementation is in development as a <a class="reference external" href="https://github.com/python/cpython/pull/115153">draft pull request on GitHub</a>.</p>
|
||
</section>
|
||
<section id="copyright">
|
||
<h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2>
|
||
<p>This document is placed in the public domain or under the
|
||
CC0-1.0-Universal license, whichever is more permissive.</p>
|
||
</section>
|
||
</section>
|
||
<hr class="docutils" />
|
||
<p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0667.rst">https://github.com/python/peps/blob/main/peps/pep-0667.rst</a></p>
|
||
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0667.rst">2024-07-03 17:01:00 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="#motivation">Motivation</a></li>
|
||
<li><a class="reference internal" href="#rationale">Rationale</a></li>
|
||
<li><a class="reference internal" href="#specification">Specification</a><ul>
|
||
<li><a class="reference internal" href="#python">Python</a></li>
|
||
<li><a class="reference internal" href="#c-api">C-API</a><ul>
|
||
<li><a class="reference internal" href="#extensions-to-the-api">Extensions to the API</a></li>
|
||
<li><a class="reference internal" href="#changes-to-existing-apis">Changes to existing APIs</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#behavior-of-f-locals-for-optimized-functions">Behavior of f_locals for optimized functions</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a><ul>
|
||
<li><a class="reference internal" href="#id1">Python</a></li>
|
||
<li><a class="reference internal" href="#id2">C-API</a><ul>
|
||
<li><a class="reference internal" href="#pyeval-getlocals">PyEval_GetLocals</a></li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#implementation">Implementation</a><ul>
|
||
<li><a class="reference internal" href="#id3">C API</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#impact-on-pep-709-inlined-comprehensions">Impact on PEP 709 inlined comprehensions</a></li>
|
||
<li><a class="reference internal" href="#comparison-with-pep-558">Comparison with PEP 558</a></li>
|
||
<li><a class="reference internal" href="#id4">Implementation</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-0667.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> |