CINXE.COM
PEP 707 – A simplified signature for __exit__ and __aexit__ | peps.python.org
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="color-scheme" content="light dark"> <title>PEP 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:description" content="This PEP proposes to make the interpreter accept context managers whose __exit__() / __aexit__() method takes only a single exception instance, while continuing to also support the current (typ, exc, tb) signature for backwards compatibility."> <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="This PEP proposes to make the interpreter accept context managers whose __exit__() / __aexit__() method takes only a single exception instance, while continuing to also support the current (typ, exc, tb) signature for backwards compatibility."> <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="w"> </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="w"> </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="w"> </span><span class="nc">C</span><span class="p">:</span> <span class="gp">... </span> <span class="k">def</span><span class="w"> </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="w"> </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="w"> </span><span class="nc">C</span><span class="p">:</span> <span class="gp">... </span> <span class="k">def</span><span class="w"> </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="w"> </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="w"> </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="w"> </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">2025-02-01 08:55:40 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>