mirror of https://github.com/python/peps
510 lines
45 KiB
HTML
510 lines
45 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 707 – A simplified signature for __exit__ and __aexit__ | peps.python.org</title>
|
||
<link rel="shortcut icon" href="../_static/py.png">
|
||
<link rel="canonical" href="https://peps.python.org/pep-0707/">
|
||
<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 707 – A simplified signature for __exit__ and __aexit__ | peps.python.org'>
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:url" content="https://peps.python.org/pep-0707/">
|
||
<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="Python Enhancement Proposals (PEPs)">
|
||
<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 707</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 707 – A simplified signature for __exit__ and __aexit__</h1>
|
||
<dl class="rfc2822 field-list simple">
|
||
<dt class="field-odd">Author<span class="colon">:</span></dt>
|
||
<dd class="field-odd">Irit Katriel <irit at python.org></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/24402">Discourse thread</a></dd>
|
||
<dt class="field-odd">Status<span class="colon">:</span></dt>
|
||
<dd class="field-odd"><abbr title="Formally declined and will not be accepted">Rejected</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">18-Feb-2023</dd>
|
||
<dt class="field-even">Python-Version<span class="colon">:</span></dt>
|
||
<dd class="field-even">3.12</dd>
|
||
<dt class="field-odd">Post-History<span class="colon">:</span></dt>
|
||
<dd class="field-odd"><a class="reference external" href="https://discuss.python.org/t/24402/" title="Discourse thread">02-Mar-2023</a></dd>
|
||
<dt class="field-even">Resolution<span class="colon">:</span></dt>
|
||
<dd class="field-even"><a class="reference external" href="https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/46">Discourse message</a></dd>
|
||
</dl>
|
||
<hr class="docutils" />
|
||
<section id="contents">
|
||
<details><summary>Table of Contents</summary><ul class="simple">
|
||
<li><a class="reference internal" href="#rejection-notice">Rejection Notice</a></li>
|
||
<li><a class="reference internal" href="#abstract">Abstract</a></li>
|
||
<li><a class="reference internal" href="#motivation">Motivation</a><ul>
|
||
<li><a class="reference internal" href="#simplify-the-implementation-of-the-language">Simplify the implementation of the language</a></li>
|
||
<li><a class="reference internal" href="#simplify-the-language-itself">Simplify the language itself</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#rationale">Rationale</a></li>
|
||
<li><a class="reference internal" href="#specification">Specification</a></li>
|
||
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></li>
|
||
<li><a class="reference internal" href="#security-implications">Security Implications</a></li>
|
||
<li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a></li>
|
||
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
|
||
<li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a><ul>
|
||
<li><a class="reference internal" href="#support-leave-self-exc">Support <code class="docutils literal notranslate"><span class="pre">__leave__(self,</span> <span class="pre">exc)</span></code></a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#copyright">Copyright</a></li>
|
||
</ul>
|
||
</details></section>
|
||
<section id="rejection-notice">
|
||
<h2><a class="toc-backref" href="#rejection-notice" role="doc-backlink">Rejection Notice</a></h2>
|
||
<p><a class="reference external" href="https://discuss.python.org/t/24402/46">Per the SC</a>:</p>
|
||
<blockquote>
|
||
<div>We discussed the PEP and have decided to reject it. Our thinking was the
|
||
magic and risk of potential breakage didn’t warrant the benefits. We are
|
||
totally supportive, though, of exploring a potential context manager v2
|
||
API or <code class="docutils literal notranslate"><span class="pre">__leave__</span></code>.</div></blockquote>
|
||
</section>
|
||
<section id="abstract">
|
||
<h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2>
|
||
<p>This PEP proposes to make the interpreter accept context managers whose
|
||
<a class="reference external" href="https://docs.python.org/3.11/reference/datamodel.html#object.__exit__" title="(in Python v3.11)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__exit__()</span></code></a> / <a class="reference external" href="https://docs.python.org/3.11/reference/datamodel.html#object.__aexit__" title="(in Python v3.11)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__aexit__()</span></code></a> method
|
||
takes only a single exception instance,
|
||
while continuing to also support the current <code class="docutils literal notranslate"><span class="pre">(typ,</span> <span class="pre">exc,</span> <span class="pre">tb)</span></code> signature
|
||
for backwards compatibility.</p>
|
||
<p>This proposal is part of an ongoing effort to remove the redundancy of
|
||
the 3-item exception representation from the language, a relic of earlier
|
||
Python versions which now confuses language users while adding complexity
|
||
and overhead to the interpreter.</p>
|
||
<p>The proposed implementation uses introspection, which is tailored to the
|
||
requirements of this use case. The solution ensures the safety of the new
|
||
feature by supporting it only in non-ambiguous cases. In particular, any
|
||
signature that <em>could</em> accept three arguments is assumed to expect them.</p>
|
||
<p>Because reliable introspection of callables is not currently possible in
|
||
Python, the solution proposed here is limited in that only the common types
|
||
of single-arg callables will be identified as such, while some of the more
|
||
esoteric ones will continue to be called with three arguments. This
|
||
imperfect solution was chosen among several imperfect alternatives in the
|
||
spirit of practicality. It is my hope that the discussion about this PEP
|
||
will explore the other options and lead us to the best way forward, which
|
||
may well be to remain with our imperfect status quo.</p>
|
||
</section>
|
||
<section id="motivation">
|
||
<h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2>
|
||
<p>In the past, an exception was represented in many parts of Python by a
|
||
tuple of three elements: the type of the exception, its value, and its
|
||
traceback. While there were good reasons for this design at the time,
|
||
they no longer hold because the type and traceback can now be reliably
|
||
deduced from the exception instance. Over the last few years we saw
|
||
several efforts to simplify the representation of exceptions.</p>
|
||
<p>Since 3.10 in <a class="reference external" href="https://github.com/python/cpython/issues/70577">CPython PR #70577</a>,
|
||
the <a class="reference external" href="https://docs.python.org/3.11/library/traceback.html#module-traceback" title="(in Python v3.11)"><code class="docutils literal notranslate"><span class="pre">traceback</span></code></a> module’s functions accept either a 3-tuple
|
||
as described above, or just an exception instance as a single argument.</p>
|
||
<p>Internally, the interpreter no longer represents exceptions as a triplet.
|
||
This was <a class="reference external" href="https://github.com/python/cpython/pull/30122">removed for the handled exception in 3.11</a> and
|
||
<a class="reference external" href="https://github.com/python/cpython/pull/101607">for the raised exception in 3.12</a>. As a consequence,
|
||
several APIs that expose the triplet can now be replaced by
|
||
simpler alternatives:</p>
|
||
<table class="docutils align-default">
|
||
<thead>
|
||
<tr class="row-odd"><th class="head"></th>
|
||
<th class="head">Legacy API</th>
|
||
<th class="head">Alternative</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="row-even"><td>Get handled exception (Python)</td>
|
||
<td><a class="reference external" href="https://docs.python.org/3.12/library/sys.html#sys.exc_info" title="(in Python v3.12)"><code class="docutils literal notranslate"><span class="pre">sys.exc_info()</span></code></a></td>
|
||
<td><a class="reference external" href="https://docs.python.org/3.12/library/sys.html#sys.exception" title="(in Python v3.12)"><code class="docutils literal notranslate"><span class="pre">sys.exception()</span></code></a></td>
|
||
</tr>
|
||
<tr class="row-odd"><td>Get handled exception (C)</td>
|
||
<td><a class="reference external" href="https://docs.python.org/3.12/c-api/exceptions.html#c.PyErr_GetExcInfo" title="(in Python v3.12)"><code class="xref c c-func docutils literal notranslate"><span class="pre">PyErr_GetExcInfo()</span></code></a></td>
|
||
<td><a class="reference external" href="https://docs.python.org/3.12/c-api/exceptions.html#c.PyErr_GetHandledException" title="(in Python v3.12)"><code class="xref c c-func docutils literal notranslate"><span class="pre">PyErr_GetHandledException()</span></code></a></td>
|
||
</tr>
|
||
<tr class="row-even"><td>Set handled exception (C)</td>
|
||
<td><a class="reference external" href="https://docs.python.org/3.12/c-api/exceptions.html#c.PyErr_SetExcInfo" title="(in Python v3.12)"><code class="xref c c-func docutils literal notranslate"><span class="pre">PyErr_SetExcInfo()</span></code></a></td>
|
||
<td><a class="reference external" href="https://docs.python.org/3.12/c-api/exceptions.html#c.PyErr_SetHandledException" title="(in Python v3.12)"><code class="xref c c-func docutils literal notranslate"><span class="pre">PyErr_SetHandledException()</span></code></a></td>
|
||
</tr>
|
||
<tr class="row-odd"><td>Get raised exception (C)</td>
|
||
<td><a class="reference external" href="https://docs.python.org/3.12/c-api/exceptions.html#c.PyErr_Fetch" title="(in Python v3.12)"><code class="xref c c-func docutils literal notranslate"><span class="pre">PyErr_Fetch()</span></code></a></td>
|
||
<td><a class="reference external" href="https://docs.python.org/3.12/c-api/exceptions.html#c.PyErr_GetRaisedException" title="(in Python v3.12)"><code class="xref c c-func docutils literal notranslate"><span class="pre">PyErr_GetRaisedException()</span></code></a></td>
|
||
</tr>
|
||
<tr class="row-even"><td>Set raised exception (C)</td>
|
||
<td><a class="reference external" href="https://docs.python.org/3.12/c-api/exceptions.html#c.PyErr_Restore" title="(in Python v3.12)"><code class="xref c c-func docutils literal notranslate"><span class="pre">PyErr_Restore()</span></code></a></td>
|
||
<td><a class="reference external" href="https://docs.python.org/3.12/c-api/exceptions.html#c.PyErr_SetRaisedException" title="(in Python v3.12)"><code class="xref c c-func docutils literal notranslate"><span class="pre">PyErr_SetRaisedException()</span></code></a></td>
|
||
</tr>
|
||
<tr class="row-odd"><td>Construct an exception instance from the 3-tuple (C)</td>
|
||
<td><a class="reference external" href="https://docs.python.org/3.12/c-api/exceptions.html#c.PyErr_NormalizeException" title="(in Python v3.12)"><code class="xref c c-func docutils literal notranslate"><span class="pre">PyErr_NormalizeException()</span></code></a></td>
|
||
<td>N/A</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>The current proposal is a step in this process, and considers the way
|
||
forward for one more case in which the 3-tuple representation has
|
||
leaked to the language. The motivation for all this work is twofold.</p>
|
||
<section id="simplify-the-implementation-of-the-language">
|
||
<h3><a class="toc-backref" href="#simplify-the-implementation-of-the-language" role="doc-backlink">Simplify the implementation of the language</a></h3>
|
||
<p>The simplification gained by reducing the interpreter’s internal
|
||
representation of the handled exception to a single object was significant.
|
||
Previously, the interpreter needed to push onto/pop
|
||
from the stack three items whenever it did anything with exceptions.
|
||
This increased stack depth (adding pressure on caches and registers) and
|
||
complicated some of the bytecodes. Reducing this to one item
|
||
<a class="reference external" href="https://github.com/python/cpython/pull/30122">removed about 100 lines of code</a>
|
||
from <code class="docutils literal notranslate"><span class="pre">ceval.c</span></code> (the interpreter’s eval loop implementation), and it was later
|
||
followed by the removal of the <code class="docutils literal notranslate"><span class="pre">POP_EXCEPT_AND_RERAISE</span></code> opcode which has
|
||
become simple enough to be <a class="reference external" href="https://github.com/python/cpython/issues/90360">replaced by generic stack manipulation instructions</a>. Micro-benchmarks showed
|
||
<a class="reference external" href="https://github.com/faster-cpython/ideas/issues/106#issuecomment-990172363">a speedup of about 10% for catching and raising an exception, as well as
|
||
for creating generators</a>.
|
||
To summarize, removing this redundancy in Python’s internals simplified the
|
||
interpreter and made it faster.</p>
|
||
<p>The performance of invoking <code class="docutils literal notranslate"><span class="pre">__exit__</span></code>/<code class="docutils literal notranslate"><span class="pre">__aexit__</span></code> when leaving
|
||
a context manager can be also improved by replacing a multi-arg function
|
||
call with a single-arg one. Micro-benchmarks showed that entering and exiting
|
||
a context manager with single-arg <code class="docutils literal notranslate"><span class="pre">__exit__</span></code> is about 13% faster.</p>
|
||
</section>
|
||
<section id="simplify-the-language-itself">
|
||
<h3><a class="toc-backref" href="#simplify-the-language-itself" role="doc-backlink">Simplify the language itself</a></h3>
|
||
<p>One of the reasons for the popularity of Python is its simplicity. The
|
||
<a class="reference external" href="https://docs.python.org/3.11/library/sys.html#sys.exc_info" title="(in Python v3.11)"><code class="docutils literal notranslate"><span class="pre">sys.exc_info()</span></code></a> triplet is cryptic for new learners,
|
||
and the redundancy in it is confusing for those who do understand it.</p>
|
||
<p>It will take multiple releases to get to a point where we can think of
|
||
deprecating <code class="docutils literal notranslate"><span class="pre">sys.exc_info()</span></code>. However, we can relatively quickly reach a
|
||
stage where new learners do not need to know about it, or about the 3-tuple
|
||
representation, at least until they are maintaining legacy code.</p>
|
||
</section>
|
||
</section>
|
||
<section id="rationale">
|
||
<h2><a class="toc-backref" href="#rationale" role="doc-backlink">Rationale</a></h2>
|
||
<p>The only reason to object today to the removal of the last remaining
|
||
appearances of the 3-tuple from the language is the concerns about
|
||
disruption that such changes can bring. The goal of this PEP is to propose
|
||
a safe, gradual and minimally disruptive way to make this change in the
|
||
case of <code class="docutils literal notranslate"><span class="pre">__exit__</span></code>, and with this to initiate a discussion of our options
|
||
for evolving its method signature.</p>
|
||
<p>In the case of the <a class="reference external" href="https://docs.python.org/3.11/library/traceback.html#module-traceback" title="(in Python v3.11)"><code class="docutils literal notranslate"><span class="pre">traceback</span></code></a> module’s API, evolving the
|
||
functions to have a hybrid signature is relatively straightforward and
|
||
safe. The functions take one positional and two optional arguments, and
|
||
interpret them according to their types. This is safe when sentinels
|
||
are used for default values. The signatures of callbacks, which are
|
||
defined by the user’s program, are harder to evolve.</p>
|
||
<p>The safest option is to make the user explicitly indicate which signature
|
||
the callback is expecting, by marking it with an additional attribute or
|
||
giving it a different name. For example, we could make the interpreter
|
||
look for a <code class="docutils literal notranslate"><span class="pre">__leave__</span></code> method on the context manager, and call it with
|
||
a single arg if it exists (otherwise, it looks for <code class="docutils literal notranslate"><span class="pre">__exit__</span></code> and
|
||
continues as it does now). The introspection-based alternative proposed
|
||
here intends to make it more convenient for users to write new code,
|
||
because they can just use the single-arg version and remain unaware of
|
||
the legacy API. However, if the limitations of introspection are found
|
||
to be too severe, we should consider an explicit option. Having both
|
||
<code class="docutils literal notranslate"><span class="pre">__exit__</span></code> and <code class="docutils literal notranslate"><span class="pre">__leave__</span></code> around for 5-10 years with similar
|
||
functionality is not ideal, but it is an option.</p>
|
||
<p>Let us now examine the limitations of the current proposal. It identifies
|
||
2-arg python functions and <code class="docutils literal notranslate"><span class="pre">METH_O</span></code> C functions as having a single-arg
|
||
signature, and assumes that anything else is expecting 3 args. Obviously
|
||
it is possible to create false negatives for this heuristic (single-arg
|
||
callables that it will not identify). Context managers written in this
|
||
way won’t work, they will continue to fail as they do now when their
|
||
<code class="docutils literal notranslate"><span class="pre">__exit__</span></code> function will be called with three arguments.</p>
|
||
<p>I believe that it will not be a problem in practice. First, all working
|
||
code will continue to work, so this is a limitation on new code rather
|
||
than a problem impacting existing code. Second, exotic callable types are
|
||
rarely used for <code class="docutils literal notranslate"><span class="pre">__exit__</span></code> and if one is needed, it can always be wrapped
|
||
by a plain vanilla method that delegates to the callable. For example, we
|
||
can write this:</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="fm">__enter__</span> <span class="o">=</span> <span class="k">lambda</span> <span class="bp">self</span><span class="p">:</span> <span class="bp">self</span>
|
||
<span class="fm">__exit__</span> <span class="o">=</span> <span class="n">ExoticCallable</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>as follows:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">CM</span><span class="p">:</span>
|
||
<span class="fm">__enter__</span> <span class="o">=</span> <span class="k">lambda</span> <span class="bp">self</span><span class="p">:</span> <span class="bp">self</span>
|
||
<span class="n">_exit</span> <span class="o">=</span> <span class="n">ExoticCallable</span><span class="p">()</span>
|
||
<span class="fm">__exit__</span> <span class="o">=</span> <span class="k">lambda</span> <span class="bp">self</span><span class="p">,</span> <span class="n">exc</span><span class="p">:</span> <span class="n">CM</span><span class="o">.</span><span class="n">_exit</span><span class="p">(</span><span class="n">exc</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>While discussing the real-world impact of the problem in this PEP, it is
|
||
worth noting that most <code class="docutils literal notranslate"><span class="pre">__exit__</span></code> functions don’t do anything with their
|
||
arguments. Typically, a context manager is implemented to ensure that some
|
||
cleanup actions take place upon exit. It is rarely appropriate for the
|
||
<code class="docutils literal notranslate"><span class="pre">__exit__</span></code> function to handle exceptions raised within the context, and
|
||
they are typically allowed to propagate out of <code class="docutils literal notranslate"><span class="pre">__exit__</span></code> to the calling
|
||
function. This means that most <code class="docutils literal notranslate"><span class="pre">__exit__</span></code> functions do not access their
|
||
arguments at all, and we should take this into account when trying to
|
||
assess the impact of different solutions on Python’s userbase.</p>
|
||
</section>
|
||
<section id="specification">
|
||
<h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2>
|
||
<p>A context manager’s <code class="docutils literal notranslate"><span class="pre">__exit__</span></code>/<code class="docutils literal notranslate"><span class="pre">__aexit__</span></code> method can have a single-arg
|
||
signature, in which case it is invoked by the interpreter with the argument
|
||
equal to an exception instance or <code class="docutils literal notranslate"><span class="pre">None</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">C</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="fm">__enter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">return</span> <span class="bp">self</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="fm">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">exc</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'__exit__ called with: </span><span class="si">{</span><span class="n">exc</span><span class="si">!r}</span><span class="s1">'</span><span class="p">)</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="k">with</span> <span class="n">C</span><span class="p">():</span>
|
||
<span class="gp">... </span> <span class="k">pass</span>
|
||
<span class="gp">...</span>
|
||
<span class="go">__exit__ called with: None</span>
|
||
<span class="gp">>>> </span><span class="k">with</span> <span class="n">C</span><span class="p">():</span>
|
||
<span class="gp">... </span> <span class="mi">1</span><span class="o">/</span><span class="mi">0</span>
|
||
<span class="gp">...</span>
|
||
<span class="go">__exit__ called with: ZeroDivisionError('division by zero')</span>
|
||
<span class="gt">Traceback (most recent call last):</span>
|
||
File <span class="nb">"<stdin>"</span>, line <span class="m">2</span>, in <span class="n"><module></span>
|
||
<span class="gr">ZeroDivisionError</span>: <span class="n">division by zero</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>If <code class="docutils literal notranslate"><span class="pre">__exit__</span></code>/<code class="docutils literal notranslate"><span class="pre">__aexit__</span></code> has any other signature, it is invoked with
|
||
the 3-tuple <code class="docutils literal notranslate"><span class="pre">(typ,</span> <span class="pre">exc,</span> <span class="pre">tb)</span></code> as happens now:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">C</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="fm">__enter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">return</span> <span class="bp">self</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="fm">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">exc</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'__exit__ called with: </span><span class="si">{</span><span class="n">exc</span><span class="si">!r}</span><span class="s1">'</span><span class="p">)</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="k">with</span> <span class="n">C</span><span class="p">():</span>
|
||
<span class="gp">... </span> <span class="k">pass</span>
|
||
<span class="gp">...</span>
|
||
<span class="go">__exit__ called with: (None, None, None)</span>
|
||
<span class="gp">>>> </span><span class="k">with</span> <span class="n">C</span><span class="p">():</span>
|
||
<span class="gp">... </span> <span class="mi">1</span><span class="o">/</span><span class="mi">0</span>
|
||
<span class="gp">...</span>
|
||
<span class="go">__exit__ called with: (<class 'ZeroDivisionError'>, ZeroDivisionError('division by zero'), <traceback object at 0x1039cb570>)</span>
|
||
<span class="gt">Traceback (most recent call last):</span>
|
||
File <span class="nb">"<stdin>"</span>, line <span class="m">2</span>, in <span class="n"><module></span>
|
||
<span class="gr">ZeroDivisionError</span>: <span class="n">division by zero</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>These <code class="docutils literal notranslate"><span class="pre">__exit__</span></code> methods will also be called with a 3-tuple:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="fm">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">typ</span><span class="p">,</span> <span class="o">*</span><span class="n">exc</span><span class="p">):</span>
|
||
<span class="k">pass</span>
|
||
|
||
<span class="k">def</span> <span class="fm">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">typ</span><span class="p">,</span> <span class="n">exc</span><span class="p">,</span> <span class="n">tb</span><span class="p">):</span>
|
||
<span class="k">pass</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>A reference implementation is provided in
|
||
<a class="reference external" href="https://github.com/python/cpython/pull/101995">CPython PR #101995</a>.</p>
|
||
<p>When the interpreter reaches the end of the scope of a context manager,
|
||
and it is about to call the relevant <code class="docutils literal notranslate"><span class="pre">__exit__</span></code> or <code class="docutils literal notranslate"><span class="pre">__aexit__</span></code> function,
|
||
it instrospects this function to determine whether it is the single-arg
|
||
or the legacy 3-arg version. In the draft PR, this introspection is performed
|
||
by the <code class="docutils literal notranslate"><span class="pre">is_legacy___exit__</span></code> function:</p>
|
||
<div class="highlight-c notranslate"><div class="highlight"><pre><span></span><span class="k">static</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="nf">is_legacy___exit__</span><span class="p">(</span><span class="n">PyObject</span><span class="w"> </span><span class="o">*</span><span class="n">exit_func</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
|
||
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">PyMethod_Check</span><span class="p">(</span><span class="n">exit_func</span><span class="p">))</span><span class="w"> </span><span class="p">{</span>
|
||
<span class="w"> </span><span class="n">PyObject</span><span class="w"> </span><span class="o">*</span><span class="n">func</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">PyMethod_GET_FUNCTION</span><span class="p">(</span><span class="n">exit_func</span><span class="p">);</span>
|
||
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">PyFunction_Check</span><span class="p">(</span><span class="n">func</span><span class="p">))</span><span class="w"> </span><span class="p">{</span>
|
||
<span class="w"> </span><span class="n">PyCodeObject</span><span class="w"> </span><span class="o">*</span><span class="n">code</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">PyCodeObject</span><span class="o">*</span><span class="p">)</span><span class="n">PyFunction_GetCode</span><span class="p">(</span><span class="n">func</span><span class="p">);</span>
|
||
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">code</span><span class="o">-></span><span class="n">co_argcount</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="o">&&</span><span class="w"> </span><span class="o">!</span><span class="p">(</span><span class="n">code</span><span class="o">-></span><span class="n">co_flags</span><span class="w"> </span><span class="o">&</span><span class="w"> </span><span class="n">CO_VARARGS</span><span class="p">))</span><span class="w"> </span><span class="p">{</span>
|
||
<span class="w"> </span><span class="cm">/* Python method that expects self + one more arg */</span>
|
||
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">false</span><span class="p">;</span>
|
||
<span class="w"> </span><span class="p">}</span>
|
||
<span class="w"> </span><span class="p">}</span>
|
||
<span class="w"> </span><span class="p">}</span>
|
||
<span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">PyCFunction_Check</span><span class="p">(</span><span class="n">exit_func</span><span class="p">))</span><span class="w"> </span><span class="p">{</span>
|
||
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">PyCFunction_GET_FLAGS</span><span class="p">(</span><span class="n">exit_func</span><span class="p">)</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">METH_O</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
|
||
<span class="w"> </span><span class="cm">/* C function declared as single-arg */</span>
|
||
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">false</span><span class="p">;</span>
|
||
<span class="w"> </span><span class="p">}</span>
|
||
<span class="w"> </span><span class="p">}</span>
|
||
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">true</span><span class="p">;</span>
|
||
<span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>It is important to note that this is not a generic introspection function, but
|
||
rather one which is specifically designed for our use case. We know that
|
||
<code class="docutils literal notranslate"><span class="pre">exit_func</span></code> is an attribute of the context manager class (taken from the
|
||
type of the object that provided <code class="docutils literal notranslate"><span class="pre">__enter__</span></code>), and it is typically a function.
|
||
Furthermore, for this to be useful we need to identify enough single-arg forms,
|
||
but not necessarily all of them. What is critical for backwards compatibility is
|
||
that we will never misidentify a legacy <code class="docutils literal notranslate"><span class="pre">exit_func</span></code> as a single-arg one. So,
|
||
for example, <code class="docutils literal notranslate"><span class="pre">__exit__(self,</span> <span class="pre">*args)</span></code> and <code class="docutils literal notranslate"><span class="pre">__exit__(self,</span> <span class="pre">exc_type,</span> <span class="pre">*args)</span></code>
|
||
both have the legacy form, even though they <em>could</em> be invoked with one arg.</p>
|
||
<p>In summary, an <code class="docutils literal notranslate"><span class="pre">exit_func</span></code> will be invoke with a single arg if:</p>
|
||
<ul class="simple">
|
||
<li>It is a <code class="docutils literal notranslate"><span class="pre">PyMethod</span></code> with <code class="docutils literal notranslate"><span class="pre">argcount</span></code> <code class="docutils literal notranslate"><span class="pre">2</span></code> (to count <code class="docutils literal notranslate"><span class="pre">self</span></code>) and no vararg, or</li>
|
||
<li>it is a <code class="docutils literal notranslate"><span class="pre">PyCFunction</span></code> with the <code class="docutils literal notranslate"><span class="pre">METH_O</span></code> flag.</li>
|
||
</ul>
|
||
<p>Note that any performance cost of the introspection can be mitigated via
|
||
<a class="pep reference internal" href="../pep-0659/" title="PEP 659 – Specializing Adaptive Interpreter">specialization</a>, so it won’t be a problem if we need to make it more
|
||
sophisticated than this for some reason.</p>
|
||
</section>
|
||
<section id="backwards-compatibility">
|
||
<h2><a class="toc-backref" href="#backwards-compatibility" role="doc-backlink">Backwards Compatibility</a></h2>
|
||
<p>All context managers that previously worked will continue to work in the
|
||
same way because the interpreter will call them with three args whenever
|
||
they can accept three args. There may be context managers that previously
|
||
did not work because their <code class="docutils literal notranslate"><span class="pre">exit_func</span></code> expected one argument, so the call
|
||
to <code class="docutils literal notranslate"><span class="pre">__exit__</span></code> would have caused a <code class="docutils literal notranslate"><span class="pre">TypeError</span></code> exception to be raised,
|
||
and now the call would succeed. This could theoretically change the
|
||
behaviour of existing code, but it is unlikely to be a problem in practice.</p>
|
||
<p>The backwards compatibility concerns will show up in some cases when libraries
|
||
try to migrate their context managers from the multi-arg to the single-arg
|
||
signature. If <code class="docutils literal notranslate"><span class="pre">__exit__</span></code> or <code class="docutils literal notranslate"><span class="pre">__aexit__</span></code> is called by any code other than
|
||
the interpreter’s eval loop, the introspection does not automatically happen.
|
||
For example, this will occur where a context manager is subclassed and its
|
||
<code class="docutils literal notranslate"><span class="pre">__exit__</span></code> method is called directly from the derived <code class="docutils literal notranslate"><span class="pre">__exit__</span></code>. Such
|
||
context managers will need to migrate to the single-arg version with their
|
||
users, and may choose to offer a parallel API rather than breaking the
|
||
existing one. Alternatively, a superclass can stay with the signature
|
||
<code class="docutils literal notranslate"><span class="pre">__exit__(self,</span> <span class="pre">*args)</span></code>, and support both one and three args. Since
|
||
most context managers do not use the value of the arguments to <code class="docutils literal notranslate"><span class="pre">__exit__</span></code>,
|
||
and simply allow the exception to propagate onward, this is likely to be the
|
||
common approach.</p>
|
||
</section>
|
||
<section id="security-implications">
|
||
<h2><a class="toc-backref" href="#security-implications" role="doc-backlink">Security Implications</a></h2>
|
||
<p>I am not aware of any.</p>
|
||
</section>
|
||
<section id="how-to-teach-this">
|
||
<h2><a class="toc-backref" href="#how-to-teach-this" role="doc-backlink">How to Teach This</a></h2>
|
||
<p>The language tutorial will present the single-arg version, and the documentation
|
||
for context managers will include a section on the legacy signatures of
|
||
<code class="docutils literal notranslate"><span class="pre">__exit__</span></code> and <code class="docutils literal notranslate"><span class="pre">__aexit__</span></code>.</p>
|
||
</section>
|
||
<section id="reference-implementation">
|
||
<h2><a class="toc-backref" href="#reference-implementation" role="doc-backlink">Reference Implementation</a></h2>
|
||
<p><a class="reference external" href="https://github.com/python/cpython/pull/101995">CPython PR #101995</a>
|
||
implements the proposal of this PEP.</p>
|
||
</section>
|
||
<section id="rejected-ideas">
|
||
<h2><a class="toc-backref" href="#rejected-ideas" role="doc-backlink">Rejected Ideas</a></h2>
|
||
<section id="support-leave-self-exc">
|
||
<h3><a class="toc-backref" href="#support-leave-self-exc" role="doc-backlink">Support <code class="docutils literal notranslate"><span class="pre">__leave__(self,</span> <span class="pre">exc)</span></code></a></h3>
|
||
<p>It was considered to support a method by a new name, such as <code class="docutils literal notranslate"><span class="pre">__leave__</span></code>,
|
||
with the new signature. This basically makes the programmer explicitly declare
|
||
which signature they are intending to use, and avoid the need for introspection.</p>
|
||
<p>Different variations of this idea include different amounts of magic that can
|
||
help automate the equivalence between <code class="docutils literal notranslate"><span class="pre">__leave__</span></code> and <code class="docutils literal notranslate"><span class="pre">__exit__</span></code>. For example,
|
||
<a class="reference external" href="https://github.com/faster-cpython/ideas/issues/550#issuecomment-1410120100">Mark Shannon suggested</a>
|
||
that the type constructor would add a default implementation for each of <code class="docutils literal notranslate"><span class="pre">__exit__</span></code>
|
||
and <code class="docutils literal notranslate"><span class="pre">__leave__</span></code> whenever one of them is defined on a class. This default
|
||
implementation acts as a trampoline that calls the user’s function. This would
|
||
make inheritance work seamlessly, as well as the migration from <code class="docutils literal notranslate"><span class="pre">__exit__</span></code> to
|
||
<code class="docutils literal notranslate"><span class="pre">__leave__</span></code> for particular classes. The interpreter would just need to call
|
||
<code class="docutils literal notranslate"><span class="pre">__leave__</span></code>, and that would call <code class="docutils literal notranslate"><span class="pre">__exit__</span></code> whenever necessary.</p>
|
||
<p>While this suggestion has several advantages over the current proposal, it has
|
||
two drawbacks. The first is that it adds a new dunder name to the data model,
|
||
and we would end up with two dunders that mean the same thing, and only slightly
|
||
differ in their signatures. The second is that it would require the migration of
|
||
every <code class="docutils literal notranslate"><span class="pre">__exit__</span></code> to <code class="docutils literal notranslate"><span class="pre">__leave__</span></code>, while with introspection it would not be
|
||
necessary to change the many <code class="docutils literal notranslate"><span class="pre">__exit__(*arg)</span></code> methods that do not access their
|
||
args. While it is not as simple as a grep for <code class="docutils literal notranslate"><span class="pre">__exit__</span></code>, it is possible to write
|
||
an AST visitor that detects <code class="docutils literal notranslate"><span class="pre">__exit__</span></code> methods that can accept multiple arguments,
|
||
and which do access them.</p>
|
||
</section>
|
||
</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-0707.rst">https://github.com/python/peps/blob/main/peps/pep-0707.rst</a></p>
|
||
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0707.rst">2023-10-10 15:15:34 GMT</a></p>
|
||
|
||
</article>
|
||
<nav id="pep-sidebar">
|
||
<h2>Contents</h2>
|
||
<ul>
|
||
<li><a class="reference internal" href="#rejection-notice">Rejection Notice</a></li>
|
||
<li><a class="reference internal" href="#abstract">Abstract</a></li>
|
||
<li><a class="reference internal" href="#motivation">Motivation</a><ul>
|
||
<li><a class="reference internal" href="#simplify-the-implementation-of-the-language">Simplify the implementation of the language</a></li>
|
||
<li><a class="reference internal" href="#simplify-the-language-itself">Simplify the language itself</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#rationale">Rationale</a></li>
|
||
<li><a class="reference internal" href="#specification">Specification</a></li>
|
||
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></li>
|
||
<li><a class="reference internal" href="#security-implications">Security Implications</a></li>
|
||
<li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a></li>
|
||
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
|
||
<li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a><ul>
|
||
<li><a class="reference internal" href="#support-leave-self-exc">Support <code class="docutils literal notranslate"><span class="pre">__leave__(self,</span> <span class="pre">exc)</span></code></a></li>
|
||
</ul>
|
||
</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-0707.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> |