981 lines
95 KiB
HTML
981 lines
95 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="Accepted and implementation complete, or no longer active">Final</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, 22-Feb-2024</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/46631/25">25-Apr-2024</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><ul>
|
|||
|
<li><a class="reference internal" href="#making-the-frame-f-locals-attribute-a-write-through-proxy">Making the <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> attribute a write-through proxy</a></li>
|
|||
|
<li><a class="reference internal" href="#making-the-locals-builtin-return-independent-snapshots">Making the <code class="docutils literal notranslate"><span class="pre">locals()</span></code> builtin return independent snapshots</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a class="reference internal" href="#specification">Specification</a><ul>
|
|||
|
<li><a class="reference internal" href="#python-api">Python API</a><ul>
|
|||
|
<li><a class="reference internal" href="#the-frame-f-locals-attribute">The <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> attribute</a></li>
|
|||
|
<li><a class="reference internal" href="#the-locals-builtin">The <code class="docutils literal notranslate"><span class="pre">locals()</span></code> builtin</a></li>
|
|||
|
<li><a class="reference internal" href="#the-eval-and-exec-builtins">The <code class="docutils literal notranslate"><span class="pre">eval()</span></code> and <code class="docutils literal notranslate"><span class="pre">exec()</span></code> builtins</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a class="reference internal" href="#c-api">C API</a><ul>
|
|||
|
<li><a class="reference internal" href="#additions-to-the-pyeval-c-api">Additions to the <code class="docutils literal notranslate"><span class="pre">PyEval</span></code> C API</a></li>
|
|||
|
<li><a class="reference internal" href="#pyframe-getlocals-c-api"><code class="docutils literal notranslate"><span class="pre">PyFrame_GetLocals</span></code> C API</a></li>
|
|||
|
<li><a class="reference internal" href="#deprecated-c-apis">Deprecated C APIs</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a class="reference internal" href="#summary-of-changes">Summary of Changes</a><ul>
|
|||
|
<li><a class="reference internal" href="#python-api-changes">Python API changes</a><ul>
|
|||
|
<li><a class="reference internal" href="#frame-f-locals-changes"><code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> changes</a></li>
|
|||
|
<li><a class="reference internal" href="#locals-changes"><code class="docutils literal notranslate"><span class="pre">locals()</span></code> changes</a></li>
|
|||
|
<li><a class="reference internal" href="#eval-and-exec-changes"><code class="docutils literal notranslate"><span class="pre">eval()</span></code> and <code class="docutils literal notranslate"><span class="pre">exec()</span></code> changes</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a class="reference internal" href="#c-api-changes">C API changes</a><ul>
|
|||
|
<li><a class="reference internal" href="#pyframe-getlocals-change"><code class="docutils literal notranslate"><span class="pre">PyFrame_GetLocals</span></code> change</a></li>
|
|||
|
<li><a class="reference internal" href="#pyeval-getlocals-change"><code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals</span></code> change</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a><ul>
|
|||
|
<li><a class="reference internal" href="#python-api-compatibility">Python API compatibility</a></li>
|
|||
|
<li><a class="reference internal" href="#frame-f-locals-compatibility"><code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> compatibility</a><ul>
|
|||
|
<li><a class="reference internal" href="#locals-compatibility"><code class="docutils literal notranslate"><span class="pre">locals()</span></code> compatibility</a></li>
|
|||
|
<li><a class="reference internal" href="#impact-on-exec-and-eval">Impact on <code class="docutils literal notranslate"><span class="pre">exec()</span></code> and <code class="docutils literal notranslate"><span class="pre">eval()</span></code></a></li>
|
|||
|
<li><a class="reference internal" href="#impact-on-other-code-execution-apis-in-the-standard-library">Impact on other code execution APIs in the standard library</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a class="reference internal" href="#c-api-compatibility">C API compatibility</a><ul>
|
|||
|
<li><a class="reference internal" href="#pyeval-getlocals-compatibility"><code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals</span></code> compatibility</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a class="reference internal" href="#impact-on-pep-709-inlined-comprehensions">Impact on PEP 709 inlined comprehensions</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a class="reference internal" href="#implementation">Implementation</a><ul>
|
|||
|
<li><a class="reference internal" href="#id1">C API</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a class="reference internal" href="#implementation-notes">Implementation Notes</a></li>
|
|||
|
<li><a class="reference internal" href="#comparison-with-pep-558">Comparison with PEP 558</a></li>
|
|||
|
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
|
|||
|
<li><a class="reference internal" href="#copyright">Copyright</a></li>
|
|||
|
</ul>
|
|||
|
</details></section>
|
|||
|
<div class="pep-banner canonical-doc sticky-banner admonition important">
|
|||
|
<p class="admonition-title">Important</p>
|
|||
|
<p>This PEP is a historical document. The up-to-date, canonical documentation can now be found at <a class="reference external" href="https://docs.python.org/3.13/library/functions.html#locals" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">locals()</span></code></a>.</p>
|
|||
|
<p class="close-button">×</p>
|
|||
|
<p>See <a class="pep reference internal" href="../pep-0001/" title="PEP 1 – PEP Purpose and Guidelines">PEP 1</a> for how to propose changes.</p>
|
|||
|
</div>
|
|||
|
<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> rather than implicitly
|
|||
|
refreshing a single shared dictionary cached on the frame object.</p>
|
|||
|
</section>
|
|||
|
<section id="motivation">
|
|||
|
<span id="pep-667-motivation"></span><h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2>
|
|||
|
<p>The 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> in releases up to and
|
|||
|
including Python 3.12 is slow, inconsistent and buggy.
|
|||
|
We want to make it faster, consistent, and most importantly fix the bugs.</p>
|
|||
|
<p>For example, when attempting to manipulate local variables via frame objects:</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>, 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. Worse than that, the Python 3.12 behavior can
|
|||
|
result in strange <a class="reference external" href="https://github.com/python/cpython/issues/74929">bugs</a>.</p>
|
|||
|
<p>With this PEP both examples would print <code class="docutils literal notranslate"><span class="pre">2</span></code> as the function level
|
|||
|
change would be written directly to the optimized local variables in
|
|||
|
the frame rather than to a cached dictionary snapshot.</p>
|
|||
|
<p>There are no compensating advantages for the Python 3.12 behavior;
|
|||
|
it is unreliable and slow.</p>
|
|||
|
<p>The <code class="docutils literal notranslate"><span class="pre">locals()</span></code> builtin has its own undesirable behaviours. Refer to <a class="pep reference internal" href="../pep-0558/" title="PEP 558 – Defined semantics for locals()">PEP 558</a>
|
|||
|
for additional details on those concerns.</p>
|
|||
|
</section>
|
|||
|
<section id="rationale">
|
|||
|
<span id="pep-667-rationale"></span><h2><a class="toc-backref" href="#rationale" role="doc-backlink">Rationale</a></h2>
|
|||
|
<section id="making-the-frame-f-locals-attribute-a-write-through-proxy">
|
|||
|
<h3><a class="toc-backref" href="#making-the-frame-f-locals-attribute-a-write-through-proxy" role="doc-backlink">Making the <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> attribute a write-through proxy</a></h3>
|
|||
|
<p>The Python 3.12 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. The
|
|||
|
<code class="docutils literal notranslate"><span class="pre">PyFrame_LocalsToFast()</span></code> C API is then called by debuggers and trace
|
|||
|
functions that want to write their changes back to the array (until
|
|||
|
Python 3.11, this API was called implicitly after every trace function
|
|||
|
invocation rather than being called explicitly by the trace functions).</p>
|
|||
|
<p>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> frame attribute may not show up as
|
|||
|
modifications to local variables if <code class="docutils literal notranslate"><span class="pre">PyFrame_LocalsToFast()</span></code> is never
|
|||
|
called. Writes to local variables can get lost if a dictionary snapshot
|
|||
|
created before the variables were modified is written back to the frame
|
|||
|
(since <em>every</em> known variable stored in the snapshot is written back to
|
|||
|
the frame, even if the value stored on the frame had changed since the
|
|||
|
snapshot was taken).</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="making-the-locals-builtin-return-independent-snapshots">
|
|||
|
<h3><a class="toc-backref" href="#making-the-locals-builtin-return-independent-snapshots" role="doc-backlink">Making the <code class="docutils literal notranslate"><span class="pre">locals()</span></code> builtin return independent snapshots</a></h3>
|
|||
|
<p><a class="pep reference internal" href="../pep-0558/" title="PEP 558 – Defined semantics for locals()">PEP 558</a> considered three potential options for standardising the behavior of the
|
|||
|
<code class="docutils literal notranslate"><span class="pre">locals()</span></code> builtin in <a class="reference external" href="https://docs.python.org/3.13/glossary.html#term-optimized-scope" title="(in Python v3.13)"><span class="xref std std-term">optimized scopes</span></a>:</p>
|
|||
|
<ul class="simple">
|
|||
|
<li>retain the historical behaviour of having each call to <code class="docutils literal notranslate"><span class="pre">locals()</span></code> on a given frame
|
|||
|
update a single shared snapshot of the local variables</li>
|
|||
|
<li>make <code class="docutils literal notranslate"><span class="pre">locals()</span></code> return write-through proxy instances (similar
|
|||
|
to <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code>)</li>
|
|||
|
<li>make <code class="docutils literal notranslate"><span class="pre">locals()</span></code> return genuinely independent snapshots so that
|
|||
|
attempts to change the values of local variables via <code class="docutils literal notranslate"><span class="pre">exec()</span></code>
|
|||
|
would be <em>consistently</em> ignored rather than being accepted in some circumstances</li>
|
|||
|
</ul>
|
|||
|
<p>The last option was chosen as the one which could most easily be explained in the
|
|||
|
language reference, and memorised by users:</p>
|
|||
|
<ul class="simple">
|
|||
|
<li>the <code class="docutils literal notranslate"><span class="pre">locals()</span></code> builtin gives an instantaneous snapshot of the local variables in
|
|||
|
optimized scopes, and read/write access in other scopes; and</li>
|
|||
|
<li><code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> gives read/write access to the local variables in all scopes,
|
|||
|
including optimized scopes</li>
|
|||
|
</ul>
|
|||
|
<p>This approach allows the intent of a piece of code to be clearer than it would be if both
|
|||
|
APIs granted full read/write access in optimized scopes, even when write access wasn’t
|
|||
|
needed or desired. For additional details on this design decision, refer to <a class="pep reference internal" href="../pep-0558/" title="PEP 558 – Defined semantics for locals()">PEP 558</a>,
|
|||
|
especially the <a class="reference internal" href="../pep-0558/#pep-558-motivation"><span class="std std-ref">Motivation</span></a> section and <a class="reference internal" href="../pep-0558/#pep-558-exec-eval-impact"><span class="std std-ref">Additional considerations for eval() and exec() in optimized scopes</span></a>.</p>
|
|||
|
<p>This approach is not without its drawbacks, which are covered
|
|||
|
in the Backwards Compatibility section below.</p>
|
|||
|
</section>
|
|||
|
</section>
|
|||
|
<section id="specification">
|
|||
|
<h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2>
|
|||
|
<section id="python-api">
|
|||
|
<h3><a class="toc-backref" href="#python-api" role="doc-backlink">Python API</a></h3>
|
|||
|
<section id="the-frame-f-locals-attribute">
|
|||
|
<span id="pep-667-f-locals-spec"></span><h4><a class="toc-backref" href="#the-frame-f-locals-attribute" role="doc-backlink">The <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> attribute</a></h4>
|
|||
|
<p>For module and class scopes (including <code class="docutils literal notranslate"><span class="pre">exec()</span></code> and <code class="docutils literal notranslate"><span class="pre">eval()</span></code>
|
|||
|
invocations), <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> is a direct
|
|||
|
reference to the local variable namespace used in code execution.</p>
|
|||
|
<p>For function scopes (and other <a class="reference external" href="https://docs.python.org/3.13/glossary.html#term-optimized-scope" title="(in Python v3.13)"><span class="xref std std-term">optimized scopes</span></a>)
|
|||
|
it will be an instance of a new write-through proxy type that can directly modify
|
|||
|
the optimized local variable storage array in the underlying frame, as well as the
|
|||
|
contents of any cell references to non-local variables.</p>
|
|||
|
<p>The view objects fully implement the <code class="docutils literal notranslate"><span class="pre">collections.abc.Mapping</span></code> interface,
|
|||
|
and also implement the following mutable mapping operations:</p>
|
|||
|
<ul class="simple">
|
|||
|
<li>using assignment to add new key/value pairs</li>
|
|||
|
<li>using assignment to update the value associated with a key</li>
|
|||
|
<li>conditional assignment via the <code class="docutils literal notranslate"><span class="pre">setdefault()</span></code> method</li>
|
|||
|
<li>bulk updates via the <code class="docutils literal notranslate"><span class="pre">update()</span></code> method</li>
|
|||
|
</ul>
|
|||
|
<p>Views of different frames compare unequal even if they have the same contents.</p>
|
|||
|
<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.</p>
|
|||
|
<p>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. New names added via the proxies
|
|||
|
will be stored in a dedicated shared dictionary stored on the
|
|||
|
underlying frame object (so all proxy instances for a given frame
|
|||
|
will be able to access any names added this way).</p>
|
|||
|
<p>Extra keys (which do not correspond to local variables on the underlying
|
|||
|
frame) may be removed as usual with <code class="docutils literal notranslate"><span class="pre">del</span></code> statements or the <code class="docutils literal notranslate"><span class="pre">pop()</span></code>
|
|||
|
method.</p>
|
|||
|
<p>Using <code class="docutils literal notranslate"><span class="pre">del</span></code>, or the <code class="docutils literal notranslate"><span class="pre">pop()</span></code> method, to remove keys that correspond to local
|
|||
|
variables on the underlying frame is NOT supported, and attempting to do so
|
|||
|
will raise <code class="docutils literal notranslate"><span class="pre">ValueError</span></code>.
|
|||
|
Local variables can only be set to <code class="docutils literal notranslate"><span class="pre">None</span></code> (or some other value) via the proxy,
|
|||
|
they cannot be unbound completely.</p>
|
|||
|
<p>The <code class="docutils literal notranslate"><span class="pre">clear()</span></code> method is NOT implemented on the write-through proxies, as it
|
|||
|
is unclear how it should handle the inability to delete entries corresponding
|
|||
|
to local variables.</p>
|
|||
|
<p>To maintain backwards compatibility, proxy APIs that need to produce a
|
|||
|
new mapping (such as <code class="docutils literal notranslate"><span class="pre">copy()</span></code>) will produce regular builtin <code class="docutils literal notranslate"><span class="pre">dict</span></code>
|
|||
|
instances, rather than write-through proxy instances.</p>
|
|||
|
<p>To avoid introducing a circular reference between frame objects and the
|
|||
|
write-through proxies, each access to <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> returns a <em>new</em>
|
|||
|
write-through proxy instance.</p>
|
|||
|
</section>
|
|||
|
<section id="the-locals-builtin">
|
|||
|
<h4><a class="toc-backref" href="#the-locals-builtin" role="doc-backlink">The <code class="docutils literal notranslate"><span class="pre">locals()</span></code> builtin</a></h4>
|
|||
|
<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_optimized</span><span class="p">():</span> <span class="c1"># Not an actual frame method</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>For module and class scopes (including <code class="docutils literal notranslate"><span class="pre">exec()</span></code> and <code class="docutils literal notranslate"><span class="pre">eval()</span></code>
|
|||
|
invocations), <code class="docutils literal notranslate"><span class="pre">locals()</span></code> continues to return a direct
|
|||
|
reference to the local variable namespace used in code execution
|
|||
|
(which is also the same value reported by <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code>).</p>
|
|||
|
<p>In <a class="reference external" href="https://docs.python.org/3.13/glossary.html#term-optimized-scope" title="(in Python v3.13)"><span class="xref std std-term">optimized scopes</span></a>,
|
|||
|
each call to <code class="docutils literal notranslate"><span class="pre">locals()</span></code> will produce an <em>independent</em>
|
|||
|
snapshot of the local variables.</p>
|
|||
|
</section>
|
|||
|
<section id="the-eval-and-exec-builtins">
|
|||
|
<h4><a class="toc-backref" href="#the-eval-and-exec-builtins" role="doc-backlink">The <code class="docutils literal notranslate"><span class="pre">eval()</span></code> and <code class="docutils literal notranslate"><span class="pre">exec()</span></code> builtins</a></h4>
|
|||
|
<p>Because this PEP changes the behavior of <code class="docutils literal notranslate"><span class="pre">locals()</span></code>, the
|
|||
|
behavior of <code class="docutils literal notranslate"><span class="pre">eval()</span></code> and <code class="docutils literal notranslate"><span class="pre">exec()</span></code> also changes.</p>
|
|||
|
<p>Assuming a function <code class="docutils literal notranslate"><span class="pre">_eval()</span></code> which performs the job of
|
|||
|
<code class="docutils literal notranslate"><span class="pre">eval()</span></code> with explicit namespace arguments, <code class="docutils literal notranslate"><span class="pre">eval()</span></code>
|
|||
|
can be defined as follows:</p>
|
|||
|
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">FrameProxyType</span> <span class="o">=</span> <span class="nb">type</span><span class="p">((</span><span class="k">lambda</span><span class="p">:</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="k">def</span> <span class="nf">eval</span><span class="p">(</span><span class="n">expression</span><span class="p">,</span> <span class="o">/</span><span class="p">,</span> <span class="nb">globals</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="nb">locals</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
|||
|
<span class="k">if</span> <span class="nb">globals</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
|||
|
<span class="c1"># No globals -> use calling frame's globals</span>
|
|||
|
<span class="n">_calling_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="nb">globals</span> <span class="o">=</span> <span class="n">_calling_frame</span><span class="o">.</span><span class="n">f_globals</span>
|
|||
|
<span class="k">if</span> <span class="nb">locals</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
|||
|
<span class="c1"># No globals or locals -> use calling frame's locals</span>
|
|||
|
<span class="nb">locals</span> <span class="o">=</span> <span class="n">_calling_frame</span><span class="o">.</span><span class="n">f_locals</span>
|
|||
|
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="nb">locals</span><span class="p">,</span> <span class="n">FrameProxyType</span><span class="p">):</span>
|
|||
|
<span class="c1"># Align with locals() builtin in optimized frame</span>
|
|||
|
<span class="nb">locals</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="nb">locals</span><span class="p">)</span>
|
|||
|
<span class="k">elif</span> <span class="nb">locals</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
|||
|
<span class="c1"># Globals but no locals -> use same namespace for both</span>
|
|||
|
<span class="nb">locals</span> <span class="o">=</span> <span class="nb">globals</span>
|
|||
|
<span class="k">return</span> <span class="n">_eval</span><span class="p">(</span><span class="n">expression</span><span class="p">,</span> <span class="nb">globals</span><span class="p">,</span> <span class="nb">locals</span><span class="p">)</span>
|
|||
|
</pre></div>
|
|||
|
</div>
|
|||
|
<p>The specified argument handling for <code class="docutils literal notranslate"><span class="pre">exec()</span></code> is similarly updated.</p>
|
|||
|
<p>(In Python 3.12 and earlier, it was not possible to provide <code class="docutils literal notranslate"><span class="pre">locals</span></code>
|
|||
|
to <code class="docutils literal notranslate"><span class="pre">eval()</span></code> or <code class="docutils literal notranslate"><span class="pre">exec()</span></code> without also providing <code class="docutils literal notranslate"><span class="pre">globals</span></code> as these
|
|||
|
were previously positional-only arguments. Independently of this
|
|||
|
PEP, Python 3.13 updated these builtins to accept keyword arguments)</p>
|
|||
|
</section>
|
|||
|
</section>
|
|||
|
<section id="c-api">
|
|||
|
<h3><a class="toc-backref" href="#c-api" role="doc-backlink">C API</a></h3>
|
|||
|
<section id="additions-to-the-pyeval-c-api">
|
|||
|
<h4><a class="toc-backref" href="#additions-to-the-pyeval-c-api" role="doc-backlink">Additions to the <code class="docutils literal notranslate"><span class="pre">PyEval</span></code> C 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 of these functions will return a new reference.</p>
|
|||
|
</section>
|
|||
|
<section id="pyframe-getlocals-c-api">
|
|||
|
<h4><a class="toc-backref" href="#pyframe-getlocals-c-api" role="doc-backlink"><code class="docutils literal notranslate"><span class="pre">PyFrame_GetLocals</span></code> C API</a></h4>
|
|||
|
<p>The existing <code class="docutils literal notranslate"><span class="pre">PyFrame_GetLocals(f)</span></code> C API is equivalent to <code class="docutils literal notranslate"><span class="pre">f.f_locals</span></code>.
|
|||
|
Its return value will be as described above for accessing <code class="docutils literal notranslate"><span class="pre">f.f_locals</span></code>.</p>
|
|||
|
<p>This function returns a new reference, so it is able to accommodate the
|
|||
|
creation of a new write-through proxy instance on each call in an
|
|||
|
optimized scope.</p>
|
|||
|
</section>
|
|||
|
<section id="deprecated-c-apis">
|
|||
|
<h4><a class="toc-backref" href="#deprecated-c-apis" role="doc-backlink">Deprecated C APIs</a></h4>
|
|||
|
<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 (which return new references) 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>The following C API functions will become no-ops, and will be deprecated without
|
|||
|
replacement:</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>
|
|||
|
<p>All of the deprecated functions will be marked as deprecated in the Python 3.13 documentation.</p>
|
|||
|
<p>Of these functions, only <code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals()</span></code> poses any significant maintenance burden.
|
|||
|
Accordingly, calls to <code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals()</span></code> will emit <code class="docutils literal notranslate"><span class="pre">DeprecationWarning</span></code> in Python
|
|||
|
3.14, with a target removal date of Python 3.16 (two releases after Python 3.14).
|
|||
|
Alternatives are recommended as described in <a class="reference internal" href="#pep-667-pyeval-getlocals-compatibility"><span class="std std-ref">PyEval_GetLocals compatibility</span></a>.</p>
|
|||
|
</section>
|
|||
|
</section>
|
|||
|
</section>
|
|||
|
<section id="summary-of-changes">
|
|||
|
<h2><a class="toc-backref" href="#summary-of-changes" role="doc-backlink">Summary of Changes</a></h2>
|
|||
|
<p>This section summarises how the specified behaviour in Python 3.13 and later
|
|||
|
differs from the historical behaviour in Python 3.12 and earlier versions.</p>
|
|||
|
<section id="python-api-changes">
|
|||
|
<h3><a class="toc-backref" href="#python-api-changes" role="doc-backlink">Python API changes</a></h3>
|
|||
|
<section id="frame-f-locals-changes">
|
|||
|
<h4><a class="toc-backref" href="#frame-f-locals-changes" role="doc-backlink"><code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> changes</a></h4>
|
|||
|
<p>Consider the following 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>Given the changes in this PEP,
|
|||
|
<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.12, this example 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 will still
|
|||
|
raise <code class="docutils literal notranslate"><span class="pre">NameError</span></code>, as it does in Python 3.12.
|
|||
|
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="locals-changes">
|
|||
|
<span id="pep-667-locals-changes"></span><h4><a class="toc-backref" href="#locals-changes" role="doc-backlink"><code class="docutils literal notranslate"><span class="pre">locals()</span></code> changes</a></h4>
|
|||
|
<p>Consider the following example:</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">exec</span><span class="p">(</span><span class="s2">"x = 1"</span><span class="p">)</span>
|
|||
|
<span class="nb">print</span><span class="p">(</span><span class="nb">locals</span><span class="p">()</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"x"</span><span class="p">))</span>
|
|||
|
<span class="n">f</span><span class="p">()</span>
|
|||
|
</pre></div>
|
|||
|
</div>
|
|||
|
<p>Given the changes in this PEP, this will <em>always</em> print <code class="docutils literal notranslate"><span class="pre">None</span></code>
|
|||
|
(regardless of whether <code class="docutils literal notranslate"><span class="pre">x</span></code> is a defined local variable in the function),
|
|||
|
as the explicit call to <code class="docutils literal notranslate"><span class="pre">locals()</span></code> produces a distinct snapshot from
|
|||
|
the one implicitly used in the <code class="docutils literal notranslate"><span class="pre">exec()</span></code> call.</p>
|
|||
|
<p>In Python 3.12, the exact example shown would print <code class="docutils literal notranslate"><span class="pre">1</span></code>, but seemingly
|
|||
|
unrelated changes to the definition of the function involved could make
|
|||
|
it print <code class="docutils literal notranslate"><span class="pre">None</span></code> instead (<a class="reference internal" href="../pep-0558/#pep-558-exec-eval-impact"><span class="std std-ref">Additional considerations for eval() and exec() in optimized scopes</span></a> in PEP 558
|
|||
|
goes into more detail on that topic).</p>
|
|||
|
</section>
|
|||
|
<section id="eval-and-exec-changes">
|
|||
|
<h4><a class="toc-backref" href="#eval-and-exec-changes" role="doc-backlink"><code class="docutils literal notranslate"><span class="pre">eval()</span></code> and <code class="docutils literal notranslate"><span class="pre">exec()</span></code> changes</a></h4>
|
|||
|
<p>The primary change affecting <code class="docutils literal notranslate"><span class="pre">eval()</span></code> and <code class="docutils literal notranslate"><span class="pre">exec()</span></code> is shown
|
|||
|
in the “<a class="reference internal" href="#pep-667-locals-changes"><span class="std std-ref">locals() changes</span></a>” example: repeatedly
|
|||
|
accessing <code class="docutils literal notranslate"><span class="pre">locals()</span></code> in an optimized scope will no longer
|
|||
|
implicitly share a common underlying namespace.</p>
|
|||
|
</section>
|
|||
|
</section>
|
|||
|
<section id="c-api-changes">
|
|||
|
<h3><a class="toc-backref" href="#c-api-changes" role="doc-backlink">C API changes</a></h3>
|
|||
|
<section id="pyframe-getlocals-change">
|
|||
|
<h4><a class="toc-backref" href="#pyframe-getlocals-change" role="doc-backlink"><code class="docutils literal notranslate"><span class="pre">PyFrame_GetLocals</span></code> change</a></h4>
|
|||
|
<p><code class="docutils literal notranslate"><span class="pre">PyFrame_GetLocals</span></code> can already return arbitrary mappings in Python 3.12,
|
|||
|
as <code class="docutils literal notranslate"><span class="pre">exec()</span></code> and <code class="docutils literal notranslate"><span class="pre">eval()</span></code> accept arbitrary mappings as their <code class="docutils literal notranslate"><span class="pre">locals</span></code> argument,
|
|||
|
and metaclasses may return arbitrary mappings from their <code class="docutils literal notranslate"><span class="pre">__prepare__</span></code> methods.</p>
|
|||
|
<p>Returning a frame locals proxy in optimized scopes just adds another case where
|
|||
|
something other than a builtin dictionary will be returned.</p>
|
|||
|
</section>
|
|||
|
<section id="pyeval-getlocals-change">
|
|||
|
<h4><a class="toc-backref" href="#pyeval-getlocals-change" role="doc-backlink"><code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals</span></code> change</a></h4>
|
|||
|
<p>The semantics of <code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals()</span></code> are technically unchanged, but they do change in
|
|||
|
practice as the dictionary cached on optimized frames is no longer shared with other
|
|||
|
mechanisms for accessing the frame locals (<code class="docutils literal notranslate"><span class="pre">locals()</span></code> builtin, <code class="docutils literal notranslate"><span class="pre">PyFrame_GetLocals</span></code>
|
|||
|
function, frame <code class="docutils literal notranslate"><span class="pre">f_locals</span></code> attributes).</p>
|
|||
|
</section>
|
|||
|
</section>
|
|||
|
</section>
|
|||
|
<section id="backwards-compatibility">
|
|||
|
<h2><a class="toc-backref" href="#backwards-compatibility" role="doc-backlink">Backwards Compatibility</a></h2>
|
|||
|
<section id="python-api-compatibility">
|
|||
|
<h3><a class="toc-backref" href="#python-api-compatibility" role="doc-backlink">Python API compatibility</a></h3>
|
|||
|
<p>The implementation used in versions up to and including Python 3.12 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="frame-f-locals-compatibility">
|
|||
|
<h3><a class="toc-backref" href="#frame-f-locals-compatibility" role="doc-backlink"><code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> compatibility</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> will be <code class="docutils literal notranslate"><span class="pre">False</span></code> for optimized
|
|||
|
frames, as each access to the attribute produces a new write-through proxy
|
|||
|
instance.</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, including the
|
|||
|
addition of new variable names as mapping keys, will always be visible.</p>
|
|||
|
<section id="locals-compatibility">
|
|||
|
<h4><a class="toc-backref" href="#locals-compatibility" role="doc-backlink"><code class="docutils literal notranslate"><span class="pre">locals()</span></code> compatibility</a></h4>
|
|||
|
<p><code class="docutils literal notranslate"><span class="pre">locals()</span> <span class="pre">is</span> <span class="pre">locals()</span></code> will be <code class="docutils literal notranslate"><span class="pre">False</span></code> for optimized frames, so
|
|||
|
code like the following will raise <code class="docutils literal notranslate"><span class="pre">KeyError</span></code> instead of returning
|
|||
|
<code class="docutils literal notranslate"><span class="pre">1</span></code>:</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="nb">locals</span><span class="p">()[</span><span class="s2">"x"</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>
|
|||
|
<span class="k">return</span> <span class="nb">locals</span><span class="p">()[</span><span class="s2">"x"</span><span class="p">]</span>
|
|||
|
</pre></div>
|
|||
|
</div>
|
|||
|
<p>To continue working, such code will need to explicitly store the namespace
|
|||
|
to be modified in a local variable, rather than relying on the previous
|
|||
|
implicit caching on the frame object:</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">ns</span> <span class="o">=</span> <span class="p">{}</span>
|
|||
|
<span class="n">ns</span><span class="p">[</span><span class="s2">"x"</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>
|
|||
|
<span class="k">return</span> <span class="n">ns</span><span class="p">[</span><span class="s2">"x"</span><span class="p">]</span>
|
|||
|
</pre></div>
|
|||
|
</div>
|
|||
|
<p>While this technically isn’t a formal backwards compatibility break
|
|||
|
(since the behaviour of writing back to <code class="docutils literal notranslate"><span class="pre">locals()</span></code> was explicitly
|
|||
|
documented as undefined), there is definitely some code that relies
|
|||
|
on the existing behaviour. Accordingly, the updated behaviour will
|
|||
|
be explicitly noted in the documentation as a change and it will be
|
|||
|
covered in the Python 3.13 porting guide.</p>
|
|||
|
<p>To work with a copy of <code class="docutils literal notranslate"><span class="pre">locals()</span></code> in optimized scopes on all
|
|||
|
versions without making redundant copies on Python 3.13+, users
|
|||
|
will need to define a version-dependent helper function that only
|
|||
|
makes an explicit copy on Python versions prior to Python 3.13:</p>
|
|||
|
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">version_info</span> <span class="o">>=</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">13</span><span class="p">):</span>
|
|||
|
<span class="k">def</span> <span class="nf">_ensure_func_snapshot</span><span class="p">(</span><span class="n">d</span><span class="p">):</span>
|
|||
|
<span class="k">return</span> <span class="n">d</span> <span class="c1"># 3.13+ locals() already returns a snapshot</span>
|
|||
|
<span class="k">else</span><span class="p">:</span>
|
|||
|
<span class="k">def</span> <span class="nf">_ensure_func_snapshot</span><span class="p">(</span><span class="n">d</span><span class="p">):</span>
|
|||
|
<span class="k">return</span> <span class="nb">dict</span><span class="p">(</span><span class="n">d</span><span class="p">)</span> <span class="c1"># Create snapshot on older versions</span>
|
|||
|
|
|||
|
<span class="k">def</span> <span class="nf">f</span><span class="p">():</span>
|
|||
|
<span class="n">ns</span> <span class="o">=</span> <span class="n">_ensure_func_snapshot</span><span class="p">(</span><span class="nb">locals</span><span class="p">())</span>
|
|||
|
<span class="n">ns</span><span class="p">[</span><span class="s2">"x"</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>
|
|||
|
<span class="k">return</span> <span class="n">ns</span>
|
|||
|
</pre></div>
|
|||
|
</div>
|
|||
|
<p>In other scopes, <code class="docutils literal notranslate"><span class="pre">locals().copy()</span></code> can continue to be called
|
|||
|
unconditionally without introducing any redundant copies.</p>
|
|||
|
</section>
|
|||
|
<section id="impact-on-exec-and-eval">
|
|||
|
<h4><a class="toc-backref" href="#impact-on-exec-and-eval" role="doc-backlink">Impact on <code class="docutils literal notranslate"><span class="pre">exec()</span></code> and <code class="docutils literal notranslate"><span class="pre">eval()</span></code></a></h4>
|
|||
|
<p>Even though this PEP does not modify <code class="docutils literal notranslate"><span class="pre">exec()</span></code> or <code class="docutils literal notranslate"><span class="pre">eval()</span></code> directly,
|
|||
|
the semantic change to <code class="docutils literal notranslate"><span class="pre">locals()</span></code> impacts the behavior of <code class="docutils literal notranslate"><span class="pre">exec()</span></code>
|
|||
|
and <code class="docutils literal notranslate"><span class="pre">eval()</span></code> as they default to running code in the calling namespace.</p>
|
|||
|
<p>This poses a potential compatibility issue for some code, as with the
|
|||
|
previous implementation that returns the same dict when <code class="docutils literal notranslate"><span class="pre">locals()</span></code> is called
|
|||
|
multiple times in function scope, the following code usually worked due to
|
|||
|
the implicitly shared local variable namespace:</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">exec</span><span class="p">(</span><span class="s1">'a = 0'</span><span class="p">)</span> <span class="c1"># equivalent to exec('a = 0', globals(), locals())</span>
|
|||
|
<span class="n">exec</span><span class="p">(</span><span class="s1">'print(a)'</span><span class="p">)</span> <span class="c1"># equivalent to exec('print(a)', globals(), locals())</span>
|
|||
|
<span class="nb">print</span><span class="p">(</span><span class="nb">locals</span><span class="p">())</span> <span class="c1"># {'a': 0}</span>
|
|||
|
<span class="c1"># However, print(a) will not work here</span>
|
|||
|
<span class="n">f</span><span class="p">()</span>
|
|||
|
</pre></div>
|
|||
|
</div>
|
|||
|
<p>With the semantic changes to <code class="docutils literal notranslate"><span class="pre">locals()</span></code> in this PEP, the <code class="docutils literal notranslate"><span class="pre">exec('print(a)')'</span></code> call
|
|||
|
will fail with <code class="docutils literal notranslate"><span class="pre">NameError</span></code>, and <code class="docutils literal notranslate"><span class="pre">print(locals())</span></code> will report an empty dictionary, as
|
|||
|
each line will be using its own distinct snapshot of the local variables rather than
|
|||
|
implicitly sharing a single cached snapshot stored on the frame object.</p>
|
|||
|
<p>A shared namespace across <code class="docutils literal notranslate"><span class="pre">exec()</span></code> calls can still be obtained by using explicit
|
|||
|
namespaces rather than relying on the previously implicitly shared frame namespace:</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">ns</span> <span class="o">=</span> <span class="p">{}</span>
|
|||
|
<span class="n">exec</span><span class="p">(</span><span class="s1">'a = 0'</span><span class="p">,</span> <span class="nb">locals</span><span class="o">=</span><span class="n">ns</span><span class="p">)</span>
|
|||
|
<span class="n">exec</span><span class="p">(</span><span class="s1">'print(a)'</span><span class="p">,</span> <span class="nb">locals</span><span class="o">=</span><span class="n">ns</span><span class="p">)</span> <span class="c1"># 0</span>
|
|||
|
<span class="n">f</span><span class="p">()</span>
|
|||
|
</pre></div>
|
|||
|
</div>
|
|||
|
<p>You can even reliably change the variables in the local scope by explicitly using
|
|||
|
<code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code>, which was not possible before (even using <code class="docutils literal notranslate"><span class="pre">ctypes</span></code> to
|
|||
|
invoke <code class="docutils literal notranslate"><span class="pre">PyFrame_LocalsToFast</span></code> was subject to the state inconsistency problems
|
|||
|
discussed elsewhere in this PEP):</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">a</span> <span class="o">=</span> <span class="kc">None</span>
|
|||
|
<span class="n">exec</span><span class="p">(</span><span class="s1">'a = 0'</span><span class="p">,</span> <span class="nb">locals</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="o">.</span><span class="n">f_locals</span><span class="p">)</span>
|
|||
|
<span class="nb">print</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="c1"># 0</span>
|
|||
|
<span class="n">f</span><span class="p">()</span>
|
|||
|
</pre></div>
|
|||
|
</div>
|
|||
|
<p>The behavior of <code class="docutils literal notranslate"><span class="pre">exec()</span></code> and <code class="docutils literal notranslate"><span class="pre">eval()</span></code> for module and class scopes (including
|
|||
|
nested invocations) is not changed, as the behaviour of <code class="docutils literal notranslate"><span class="pre">locals()</span></code> in those
|
|||
|
scopes is not changing.</p>
|
|||
|
</section>
|
|||
|
<section id="impact-on-other-code-execution-apis-in-the-standard-library">
|
|||
|
<h4><a class="toc-backref" href="#impact-on-other-code-execution-apis-in-the-standard-library" role="doc-backlink">Impact on other code execution APIs in the standard library</a></h4>
|
|||
|
<p><code class="docutils literal notranslate"><span class="pre">pdb</span></code> and <code class="docutils literal notranslate"><span class="pre">bdb</span></code> use the <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> API, and hence will be able to
|
|||
|
reliably update local variables even in optimized frames. Implementing this
|
|||
|
PEP will resolve several longstanding bugs in these modules relating to threads,
|
|||
|
generators, coroutines, and other mechanisms that allow concurrent code execution
|
|||
|
while the debugger is active.</p>
|
|||
|
<p>Other code execution APIs in the standard library (such as the <code class="docutils literal notranslate"><span class="pre">code</span></code> module)
|
|||
|
do not implicitly access <code class="docutils literal notranslate"><span class="pre">locals()</span></code> <em>or</em> <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code>, but the behaviour
|
|||
|
of explicitly passing these namespaces will change as described in the rest of
|
|||
|
this PEP (passing <code class="docutils literal notranslate"><span class="pre">locals()</span></code> in optimized scopes will no longer implicitly
|
|||
|
share the code execution namespace across calls, passing <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code>
|
|||
|
in optimized scopes will allow reliable modification of local variables and
|
|||
|
nonlocal cell references).</p>
|
|||
|
</section>
|
|||
|
</section>
|
|||
|
<section id="c-api-compatibility">
|
|||
|
<h3><a class="toc-backref" href="#c-api-compatibility" role="doc-backlink">C API compatibility</a></h3>
|
|||
|
<section id="pyeval-getlocals-compatibility">
|
|||
|
<span id="pep-667-pyeval-getlocals-compatibility"></span><h4><a class="toc-backref" href="#pyeval-getlocals-compatibility" role="doc-backlink"><code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals</span></code> compatibility</a></h4>
|
|||
|
<p><code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals()</span></code> has never historically distinguished between whether it was
|
|||
|
emulating <code class="docutils literal notranslate"><span class="pre">locals()</span></code> or <code class="docutils literal notranslate"><span class="pre">sys._getframe().f_locals</span></code> at the Python level, as they all
|
|||
|
returned references to the same shared cache of the local variable bindings.</p>
|
|||
|
<p>With this PEP, <code class="docutils literal notranslate"><span class="pre">locals()</span></code> changes to return independent snapshots on each call for
|
|||
|
optimized frames, and <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> (along with <code class="docutils literal notranslate"><span class="pre">PyFrame_GetLocals</span></code>) changes to
|
|||
|
return new write-through proxy instances.</p>
|
|||
|
<p>Because <code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals()</span></code> returns a borrowed reference, it isn’t possible to update
|
|||
|
its semantics to align with either of those alternatives, leaving it as the only remaining
|
|||
|
API that requires a shared cache dictionary stored on the frame object.</p>
|
|||
|
<p>While this technically leaves the semantics of the function unchanged, it no longer allows
|
|||
|
extra dict entries to be made visible to users of the other APIs, as those APIs are no longer
|
|||
|
accessing the same underlying cache dictionary.</p>
|
|||
|
<p>When <code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals()</span></code> is being used as an equivalent to the Python <code class="docutils literal notranslate"><span class="pre">locals()</span></code>
|
|||
|
builtin, <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="o">//</span> <span class="n">Equivalent</span> <span class="n">to</span> <span class="s2">"locals()"</span> <span class="ow">in</span> <span class="n">Python</span> <span class="n">code</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>
|
|||
|
<p>When <code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals()</span></code> is being used as an equivalent to calling
|
|||
|
<code class="docutils literal notranslate"><span class="pre">sys._getframe().f_locals</span></code> in Python, it should be replaced by calling
|
|||
|
<code class="docutils literal notranslate"><span class="pre">PyFrame_GetLocals()</span></code> on the result of <code class="docutils literal notranslate"><span class="pre">PyEval_GetFrame()</span></code>.</p>
|
|||
|
<p>In these cases, the original code should be replaced with:</p>
|
|||
|
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="o">//</span> <span class="n">Equivalent</span> <span class="n">to</span> <span class="s2">"sys._getframe()"</span> <span class="ow">in</span> <span class="n">Python</span> <span class="n">code</span>
|
|||
|
<span class="n">frame</span> <span class="o">=</span> <span class="n">PyEval_GetFrame</span><span class="p">();</span>
|
|||
|
<span class="k">if</span> <span class="p">(</span><span class="n">frame</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="o">//</span> <span class="n">Equivalent</span> <span class="n">to</span> <span class="s2">"frame.f_locals"</span> <span class="ow">in</span> <span class="n">Python</span> <span class="n">code</span>
|
|||
|
<span class="nb">locals</span> <span class="o">=</span> <span class="n">PyFrame_GetLocals</span><span class="p">(</span><span class="n">frame</span><span class="p">);</span>
|
|||
|
<span class="n">frame</span> <span class="o">=</span> <span class="n">NULL</span><span class="p">;</span> <span class="o">//</span> <span class="n">Minimise</span> <span class="n">visibility</span> <span class="n">of</span> <span class="n">borrowed</span> <span class="n">reference</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 id="impact-on-pep-709-inlined-comprehensions">
|
|||
|
<h3><a class="toc-backref" href="#impact-on-pep-709-inlined-comprehensions" role="doc-backlink">Impact on PEP 709 inlined comprehensions</a></h3>
|
|||
|
<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, 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>
|
|||
|
<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="id1">
|
|||
|
<h3><a class="toc-backref" href="#id1" 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">else</span> <span class="p">{</span>
|
|||
|
<span class="n">PyDict_Update</span><span class="p">(</span><span class="n">frame</span><span class="o">-></span><span class="n">_locals_cache</span><span class="p">,</span> <span class="n">PyFrame_GetLocals</span><span class="p">(</span><span class="n">frame</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="implementation-notes">
|
|||
|
<h2><a class="toc-backref" href="#implementation-notes" role="doc-backlink">Implementation Notes</a></h2>
|
|||
|
<p>When accepted, the PEP text suggested that <code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals</span></code> would start returning a
|
|||
|
cached instance of the new write-through proxy, while the implementation sketch indicated
|
|||
|
it would continue to return a dictionary snapshot cached on the frame instance. This
|
|||
|
discrepancy was identified while implementing the PEP, and
|
|||
|
<a class="reference external" href="https://github.com/python/steering-council/issues/245#issuecomment-2179005461">resolved by the Steering Council</a>
|
|||
|
in favour of retaining the Python 3.12 behaviour of returning a dictionary snapshot
|
|||
|
cached on the frame instance.
|
|||
|
The PEP text has been updated accordingly.</p>
|
|||
|
<p>During the discussions of the C API clarification, it also became apparent that the
|
|||
|
rationale behind <code class="docutils literal notranslate"><span class="pre">locals()</span></code> being updated to return independent snapshots in
|
|||
|
<a class="reference external" href="https://docs.python.org/3.13/glossary.html#term-optimized-scope" title="(in Python v3.13)"><span class="xref std std-term">optimized scopes</span></a> wasn’t clear, as it had been inherited
|
|||
|
from the original <a class="pep reference internal" href="../pep-0558/" title="PEP 558 – Defined semantics for locals()">PEP 558</a> discussions rather than being independently covered in this
|
|||
|
PEP. The PEP text has been updated to better cover this change, with additional updates
|
|||
|
to the Specification and Backwards Compatibility sections to cover the impact on code
|
|||
|
execution APIs that default to executing code in the <code class="docutils literal notranslate"><span class="pre">locals()</span></code> namespace. Additional
|
|||
|
motivation and rationale details have also been added to <a class="pep reference internal" href="../pep-0558/" title="PEP 558 – Defined semantics for locals()">PEP 558</a>.</p>
|
|||
|
<p>In 3.13.0, the write-through proxies did not allow deletion of even extra variables
|
|||
|
with <code class="docutils literal notranslate"><span class="pre">del</span></code> and <code class="docutils literal notranslate"><span class="pre">pop()</span></code>. This was subsequently reported as a
|
|||
|
<a class="reference external" href="https://github.com/python/cpython/issues/125590">compatibility regression</a>,
|
|||
|
and <a class="reference external" href="https://github.com/python/cpython/pull/125616">resolved</a> as now described
|
|||
|
in <a class="reference internal" href="#pep-667-f-locals-spec"><span class="std std-ref">The frame.f_locals attribute</span></a>.</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> shared 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 PEP 558 is that
|
|||
|
PEP 558 attempted to store extra variables inside a full
|
|||
|
internal dictionary copy of the local variables in an effort
|
|||
|
to improve backwards compatibility with the legacy
|
|||
|
<code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals()</span></code> API, whereas this PEP does not (it stores
|
|||
|
the extra local variables in a dedicated dictionary accessed
|
|||
|
solely via the new frame proxy objects, and copies them to the
|
|||
|
<code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals()</span></code> shared dict only when requested).</p>
|
|||
|
<p>PEP 558 did not specify exactly when that internal copy was
|
|||
|
updated, making the behavior of PEP 558 impossible to reason
|
|||
|
about in several cases where this PEP remains well specified.</p>
|
|||
|
<p>PEP 558 also proposed the introduction of some additional Python
|
|||
|
scope introspection interfaces to the C API that would allow
|
|||
|
extension modules to more easily determine whether the currently
|
|||
|
active Python scope is optimized or not, and hence whether
|
|||
|
the C API’s <code class="docutils literal notranslate"><span class="pre">locals()</span></code> equivalent returns a direct reference
|
|||
|
to the frame’s local execution namespace or a shallow copy of
|
|||
|
the frame’s local variables and nonlocal cell references.
|
|||
|
Whether or not to add such introspection APIs is independent
|
|||
|
of the proposed changes to <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>
|
|||
|
and hence no such proposals have been included in this PEP.</p>
|
|||
|
<p>PEP 558 was
|
|||
|
<a class="pep reference internal" href="../pep-0558/#pep-withdrawal" title="PEP 558 – Defined semantics for locals() § PEP Withdrawal">ultimately withdrawn</a>
|
|||
|
in favour of this PEP.</p>
|
|||
|
</section>
|
|||
|
<section id="reference-implementation">
|
|||
|
<h2><a class="toc-backref" href="#reference-implementation" role="doc-backlink">Reference 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-10-27 07:11:46 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><ul>
|
|||
|
<li><a class="reference internal" href="#making-the-frame-f-locals-attribute-a-write-through-proxy">Making the <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> attribute a write-through proxy</a></li>
|
|||
|
<li><a class="reference internal" href="#making-the-locals-builtin-return-independent-snapshots">Making the <code class="docutils literal notranslate"><span class="pre">locals()</span></code> builtin return independent snapshots</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a class="reference internal" href="#specification">Specification</a><ul>
|
|||
|
<li><a class="reference internal" href="#python-api">Python API</a><ul>
|
|||
|
<li><a class="reference internal" href="#the-frame-f-locals-attribute">The <code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> attribute</a></li>
|
|||
|
<li><a class="reference internal" href="#the-locals-builtin">The <code class="docutils literal notranslate"><span class="pre">locals()</span></code> builtin</a></li>
|
|||
|
<li><a class="reference internal" href="#the-eval-and-exec-builtins">The <code class="docutils literal notranslate"><span class="pre">eval()</span></code> and <code class="docutils literal notranslate"><span class="pre">exec()</span></code> builtins</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a class="reference internal" href="#c-api">C API</a><ul>
|
|||
|
<li><a class="reference internal" href="#additions-to-the-pyeval-c-api">Additions to the <code class="docutils literal notranslate"><span class="pre">PyEval</span></code> C API</a></li>
|
|||
|
<li><a class="reference internal" href="#pyframe-getlocals-c-api"><code class="docutils literal notranslate"><span class="pre">PyFrame_GetLocals</span></code> C API</a></li>
|
|||
|
<li><a class="reference internal" href="#deprecated-c-apis">Deprecated C APIs</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a class="reference internal" href="#summary-of-changes">Summary of Changes</a><ul>
|
|||
|
<li><a class="reference internal" href="#python-api-changes">Python API changes</a><ul>
|
|||
|
<li><a class="reference internal" href="#frame-f-locals-changes"><code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> changes</a></li>
|
|||
|
<li><a class="reference internal" href="#locals-changes"><code class="docutils literal notranslate"><span class="pre">locals()</span></code> changes</a></li>
|
|||
|
<li><a class="reference internal" href="#eval-and-exec-changes"><code class="docutils literal notranslate"><span class="pre">eval()</span></code> and <code class="docutils literal notranslate"><span class="pre">exec()</span></code> changes</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a class="reference internal" href="#c-api-changes">C API changes</a><ul>
|
|||
|
<li><a class="reference internal" href="#pyframe-getlocals-change"><code class="docutils literal notranslate"><span class="pre">PyFrame_GetLocals</span></code> change</a></li>
|
|||
|
<li><a class="reference internal" href="#pyeval-getlocals-change"><code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals</span></code> change</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a><ul>
|
|||
|
<li><a class="reference internal" href="#python-api-compatibility">Python API compatibility</a></li>
|
|||
|
<li><a class="reference internal" href="#frame-f-locals-compatibility"><code class="docutils literal notranslate"><span class="pre">frame.f_locals</span></code> compatibility</a><ul>
|
|||
|
<li><a class="reference internal" href="#locals-compatibility"><code class="docutils literal notranslate"><span class="pre">locals()</span></code> compatibility</a></li>
|
|||
|
<li><a class="reference internal" href="#impact-on-exec-and-eval">Impact on <code class="docutils literal notranslate"><span class="pre">exec()</span></code> and <code class="docutils literal notranslate"><span class="pre">eval()</span></code></a></li>
|
|||
|
<li><a class="reference internal" href="#impact-on-other-code-execution-apis-in-the-standard-library">Impact on other code execution APIs in the standard library</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a class="reference internal" href="#c-api-compatibility">C API compatibility</a><ul>
|
|||
|
<li><a class="reference internal" href="#pyeval-getlocals-compatibility"><code class="docutils literal notranslate"><span class="pre">PyEval_GetLocals</span></code> compatibility</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a class="reference internal" href="#impact-on-pep-709-inlined-comprehensions">Impact on PEP 709 inlined comprehensions</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a class="reference internal" href="#implementation">Implementation</a><ul>
|
|||
|
<li><a class="reference internal" href="#id1">C API</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a class="reference internal" href="#implementation-notes">Implementation Notes</a></li>
|
|||
|
<li><a class="reference internal" href="#comparison-with-pep-558">Comparison with PEP 558</a></li>
|
|||
|
<li><a class="reference internal" href="#reference-implementation">Reference 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>
|