# PEP 707: A simplified signature for \_\_exit\_\_ and \_\_aexit\_\_

**URL:** <https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402>\
**Category:** PEPs\
**Created:** [March 2, 2023, 5:26pm UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402 "2023-03-02T17:26:05Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![iritkatriel](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/iritkatriel/32/2615_2.png) [@iritkatriel](https://discuss.python.org/u/iritkatriel)\
**Post date:** [March 2, 2023, 5:26pm UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/1 "2023-03-02T17:26:05Z")

</div>

The legacy representation of exceptions as a `(type, value, traceback)` triplet was useful in the past, but now it is redundant because the type and traceback can always be deduced from the value. Over the past few years we have been working to remove this redundancy from the language and its implementation.

One of the harder problems is how to evolve the signature of context managers’ ` __exit__ `/` __aexit__ ` functions. A number of us have been bouncing ideas about this, and feel that it’s time to bring them forward so that they can be discussed. I have written [PEP 707](https://peps.python.org/pep-0707/), with my preferred option, to kick this off.

I am expecting at least one alternative proposal to be presented, hopefully even more. As I wrote in the PEP: 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.

---

<div class="post-metadata">

**Author:** ![AlexWaygood](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/alexwaygood/32/4722_2.png) [@AlexWaygood](https://discuss.python.org/u/AlexWaygood)\
**Post date:** [March 2, 2023, 6:08pm UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/2 "2023-03-02T18:08:11Z")

</div>

Thank you for this PEP! One slightly confusing thing for me: in [the “Motivations” section](https://peps.python.org/pep-0707/#motivation), `sys.exc_info` is listed in a table under a “Deprecated” column, implying that it is already deprecated. But I can’t see a note in [the docs for `sys.exc_info`](https://docs.python.org/3.12/library/sys.html#sys.exc_info) saying that it’s deprecated. And [slightly lower down](https://peps.python.org/pep-0707/#simplify-the-language-itself) in the PEP, you state:

> It will take multiple releases to get to a point where we can think of deprecating `sys.exc_info()` .

Perhaps it would be better to name the first column of the table in the “Motivations” section “Old-style APIs” or “Legacy APIs”?

---

<div class="post-metadata">

**Author:** ![storchaka](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/storchaka/32/217_2.png) [@storchaka](https://discuss.python.org/u/storchaka)\
**Post date:** [March 2, 2023, 6:17pm UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/3 "2023-03-02T18:17:17Z")

</div>

I think relying on introspection is a very wrong way.

Look at precedence of solving similar problems in the past: the pickle protocol, rich comparison, the buffer protocol. Introducing a new dunder has underwater rocks, but it is how it always was done.

---

<div class="post-metadata">

**Author:** ![iritkatriel](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/iritkatriel/32/2615_2.png) [@iritkatriel](https://discuss.python.org/u/iritkatriel)\
**Post date:** [March 3, 2023, 6:09pm UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/5 "2023-03-03T18:09:56Z")

</div>

> [@storchaka](#):
>
> Introducing a new dunder has underwater rocks, but it is how it always was done.

Thanks. Would be great if you (or others) could elaborate how that would be applied in this case.

---

<div class="post-metadata">

**Author:** ![storchaka](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/storchaka/32/217_2.png) [@storchaka](https://discuss.python.org/u/storchaka)\
**Post date:** [March 3, 2023, 6:23pm UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/6 "2023-03-03T18:23:04Z")

</div>

As I have understood it from its description in PEP 707, it is a Mark’s idea about ` __leave__ `. Although I did not read a discussion about it, only your summary.

---

<div class="post-metadata">

**Author:** ![carljm](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/carljm/32/959_2.png) [@carljm](https://discuss.python.org/u/carljm)\
**Post date:** [March 3, 2023, 6:29pm UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/7 "2023-03-03T18:29:55Z")

</div>

I feel that the rationale presented in the PEP for rejecting ` __leave__ ` (with automatic addition of the counterpart trampoline to every type that defines ` __leave__ ` or ` __exit__ `) isn’t convincing/sufficient to reject it. The only downside mentioned is that the subset of existing ` __exit__ ` methods that take the form `def __exit__ (*args):` and don’t use `*args` would need to be converted to ` __leave__ `, whereas in the introspection version they wouldn’t. But a) absent data I’m not sure how high a percentage of existing ` __exit__ ` methods this really is, and b) renaming such a method to `def __leave__ (exc)` isn’t an onerous or risky update: it’s safe and easily automatable.

The advantages of the ` __leave__ ` proposal in clarity and performance seem to me to outweigh this downside.

---

<div class="post-metadata">

**Author:** ![davidism](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/davidism/32/3295_2.png) [@davidism](https://discuss.python.org/u/davidism)\
**Post date:** [March 3, 2023, 6:46pm UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/8 "2023-03-03T18:46:08Z")

</div>

If a new name is used, there should probably be a deprecation warning when ` __exit__ ` is encountered, recommending switching to the new method.

---

<div class="post-metadata">

**Author:** ![storchaka](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/storchaka/32/217_2.png) [@storchaka](https://discuss.python.org/u/storchaka)\
**Post date:** [March 3, 2023, 7:20pm UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/9 "2023-03-03T19:20:26Z")

</div>

` __cmp__ ` and the old buffer protocol were deprecated and no longer supported, but ` __reduce__ ` and ` __getnewargs__ ` are still supported, alongside with ` __reduce_ex__ ` and ` __getnewargs_ex__ `. Deprecating ` __exit__ ` will harm a lot of people.

Replacing a triple of arguments with a single argument is a weak reason for introducing a new dunder. The first and the third arguments can be derived from the second argument, so it is only a matter of convenience. I do not think that it justifies all drawbacks.

But we can return to it if decide to extend the context manager semantic.

1. The result of ` __enter__ ` can be provided to a user, but it is not accessible in ` __exit__ `. For example, one of problems related to implementation of the buffer protocol from Python side is that ` __enter__ ` can return an acquired memory view, but ` __exit__ ` does not get it back to release it. It may be worth to pass the result of ` __enter__ ` as yet one argument to ` __leave__ `.

2. The result of ` __enter__ ` can be provided to a user, but in many cases the useful result of the context manager is only available after exiting the `with` block. Examples are `assertRaises()` and `assertWarns()`. The only solution is to return a mutable object, keep the reference to it in the context manager (see (1)), and updating it in ` __exit__ `. You cannot return an immutable object. You need to keep a reference. You cannot implement the `timeit` context manager which return just a time spent to execute the block as a float.

I think it should be a separate topic for discussing an extended context manager protocol.

---

<div class="post-metadata">

**Author:** ![iritkatriel](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/iritkatriel/32/2615_2.png) [@iritkatriel](https://discuss.python.org/u/iritkatriel)\
**Post date:** [March 3, 2023, 7:48pm UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/10 "2023-03-03T19:48:05Z")

</div>

> [@storchaka](#):
>
> I think it should be a separate topic for discussing an extended context manager protocol.

I think this is on topic here actually. Redesigning a new context manager from scratch with other improvements instead of iterating over the existing one is an option we should consider.

---

<div class="post-metadata">

**Author:** ![iritkatriel](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/iritkatriel/32/2615_2.png) [@iritkatriel](https://discuss.python.org/u/iritkatriel)\
**Post date:** [March 3, 2023, 8:24pm UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/11 "2023-03-03T20:24:38Z")

</div>

> [@carljm](#):
>
> absent data I’m not sure how high a percentage of existing ` __exit__ ` methods this really is,

In the stdlib (excluding tests) there are 85 ` __exit__ ` functions, of which 33 take a `*vararg` (two also `**kwargs`) and don’t use it at all, like:

```auto
        def __exit__ (self, *args):
            self.close()

```

Then there are 5 which take a `*vararg` and forward it to another ` __exit__ ` function, like:

```auto
    def __exit__ (self, *args):
        return self._lock. __exit__ (*args)

```

The only ` __exit__ ` I saw which takes `*vararg` and would need to change is in `contextlib._GeneratorContextManager`.

There are 46 that take 3 positional args (and may or may not use them).

Edit: I added counts for ` __exit__ ` that don’t use `*vararg` because they are also relevant.

---

<div class="post-metadata">

**Author:** ![carljm](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/carljm/32/959_2.png) [@carljm](https://discuss.python.org/u/carljm)\
**Post date:** [March 3, 2023, 9:37pm UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/12 "2023-03-03T21:37:52Z")

</div>

For another data point, I just checked in instagram server codebase: there are 87 occurrences of `def __exit__ `, and 10 of them have a `*` in the signature. I suspect many of the other 77 don’t use the exception information either, but they copied the signature from another context manager, or from Python docs, and didn’t think to replace the three args with a vararg.

---

<div class="post-metadata">

**Author:** ![markshannon](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/markshannon/32/72_2.png) [@markshannon](https://discuss.python.org/u/markshannon)\
**Post date:** [March 6, 2023, 12:59pm UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/13 "2023-03-06T12:59:53Z")

</div>

I’m strongly in favour of the ` __leave__ ` approach.

I feel that ` __leave__ ` is more robust, without the edge cases that having two versions of ` __exit__ ` has. While introspection is viable for Python and builtin functions, it breaks down for other callables, like classes.  
E.g. for a class is it the ` __new__ ` or the ` __init__ ` method that determines which variant to call?

---

<div class="post-metadata">

**Author:** ![markshannon](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/markshannon/32/72_2.png) [@markshannon](https://discuss.python.org/u/markshannon)\
**Post date:** [March 6, 2023, 1:02pm UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/14 "2023-03-06T13:02:36Z")

</div>

Whichever approach we take there will be two changes to the language:

1. From what we have now, to the one-argument-first implementation (2023)
2. Removal of support for the three argument form (~2028)

With the one-argument form of ` __exit__ `, nothing need be done to handle the first step.  
But things get complicated for the second step.

Any library that needs to support versions straddling the second transition need to dynamically handle either one or three arguments. Something like:

```plaintext
def __exit__ (self, arg0, arg1=SENTINEL, arg2=SENTINEL):
    if arg1 is SENTINEL:
        exc = arg0
    else:
        exc = arg1
    ...

```

With ` __leave__ `, libraries need to add a ` __leave__ ` method, and remove the ` __exit__ ` sometime between steps 1 and 2. More work, yes, but safer and more easily understood.

---

<div class="post-metadata">

**Author:** ![iritkatriel](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/iritkatriel/32/2615_2.png) [@iritkatriel](https://discuss.python.org/u/iritkatriel)\
**Post date:** [March 6, 2023, 1:09pm UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/15 "2023-03-06T13:09:14Z")

</div>

> [@markshannon](#):
>
> Any library that needs to support versions straddling the second transition need to dynamically handle either one or three arguments.

This is correct, in the uncommon case where the function does something with the args, otherwise `*args` works for both.

---

<div class="post-metadata">

**Author:** ![davidfstr](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/davidfstr/32/6944_2.png) [@davidfstr](https://discuss.python.org/u/davidfstr)\
**Post date:** [March 6, 2023, 4:33pm UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/16 "2023-03-06T16:33:23Z")

</div>

I generally support the initial proposal to alter ` __exit__ `'s signature:

- As an existing Python user who is familiar with the current form of ` __exit__ `, I support the approach of changing the signature of the existing ` __exit__ `. The proposed new signature (with just the exception value) is what I’ve always thought it should be anyway.

- I expect a new Python user writing code that has to interface with older library code (that uses ` __exit__ `) could easily be confused that they may need to override an “obscure” ` __exit__ ` function rather than define a (now recommended) ` __leave__ ` function.

- I expect a new Python user writing _new_ code wouldn’t care whether ` __exit__ ` or ` __leave__ ` was the recommended form, only that the recommended form was clearly documented.

* * *

Small nitpick: The link from word “specialization” in the PEP goes to PEP 564, which doesn’t appear to have anything to do with specialization. Perhaps it was intended to link to a different PEP?

---

<div class="post-metadata">

**Author:** ![stoneleaf](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/stoneleaf/32/88_2.png) [@stoneleaf](https://discuss.python.org/u/stoneleaf)\
**Post date:** [March 6, 2023, 5:58pm UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/17 "2023-03-06T17:58:22Z")

</div>

> [@markshannon](#):
>
> With ` __leave__ `, libraries need to add a ` __leave__ ` method, and remove the ` __exit__ ` sometime between steps 1 and 2. More work, yes, but safer and more easily understood.

Or, use ` __leave__ ` if it exists, ignoring ` __exit__ `; if only ` __exit__ ` exists, either issue a deprecation warning, or an error, as appropriate for that version of Python. That way ` __exit__ ` doesn’t have to be removed until the library in question decides to stop supporting Python versions that only use ` __exit__ `.

---

<div class="post-metadata">

**Author:** ![csm10495](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/csm10495/32/9731_2.png) [@csm10495](https://discuss.python.org/u/csm10495)\
**Post date:** [March 7, 2023, 3:08am UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/18 "2023-03-07T03:08:18Z")

</div>

Is there a performance hit for the proposed change?

If not (or if insignificant) I’d greatly prefer just modifying ` __exit__ ` in the proposed way. It seems weird to think about adding a ` __leave__ ` considering we’ve had exit all this time. We can even mark the old signature of ` __exit__ ` as pending deprecation (then deprecated) for a few versions if desired to notify and give time for libraries to update.

I’m not for or against that deprecation. Though I am for the proposal in the PEP.

If we did have ` __leave__ ` what would we do if both ` __exit__ ` and ` __leave__ ` were defined? I’m sure we can come up with a precedence, but I think we’re adding undo complexity and confusion for folks.

---

<div class="post-metadata">

**Author:** ![iritkatriel](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/iritkatriel/32/2615_2.png) [@iritkatriel](https://discuss.python.org/u/iritkatriel)\
**Post date:** [March 7, 2023, 10:34am UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/19 "2023-03-07T10:34:48Z")

</div>

> [@markshannon](#):
>
> While introspection is viable for Python and builtin functions, it breaks down for other callables, like classes.  
> E.g. for a class is it the ` __new__ ` or the ` __init__ ` method that determines which variant to call?

This is addressed in the PEP - only the straightforward cases are interpreted as single-arg. I don’t think this is would be a problem in practice.

> [@csm10495](#):
>
> Is there a performance hit for the proposed change?

As mentioned in the PEP, any performance impact can be mitigated with specialisation

> [@stoneleaf](#):
>
> Or, use ` __leave__ ` if it exists, ignoring ` __exit__ `; if only ` __exit__ ` exists, either issue a deprecation warning, or an error, as appropriate for that version of Python.

The interpreter can do that, but other calls to ` __exit__ `, from libraries and subclasses, need to continue working through the transition.

> [@storchaka](#):
>
> I think relying on introspection is a very wrong way.
> 
> Look at precedence of solving similar problems in the past: the pickle protocol, rich comparison, the buffer protocol. Introducing a new dunder has underwater rocks, but it is how it always was done.

As I wrote in the intro, I am not sure myself that introspection is the right approach. I also agree with you that just removing two redundant args is weak justification for a new dunder. I think your suggestion to redesign context managers from scratch should be explored. However, I would like better justification to reject the introspection approach than “that’s not what we did in the past”. What are the problems with this approach that make it unacceptable in your view?

---

<div class="post-metadata">

**Author:** ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)\
**Post date:** [March 7, 2023, 11:25am UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/20 "2023-03-07T11:25:45Z")

</div>

Does it seem better or worse if the _compiler_ checks the arguments and somehow marks the code object in a way that can be checked specifically at runtime (e.g. a `ONE_ARG_EXIT` flag or attribute)? Technically I guess it’s still introspection, but at least specified all the way through rather than trying to interpret other metadata after the fact.

Seems like potentially a bad precedent, but otherwise I’m not totally convinced it’s the worst option.

Also, if we get 13% perf improvement from dropping two arguments (presumably that’s mostly calling convention overhead?), why not also allow dropping all arguments for cases where you don’t care about what the exception is at all, or even whether one occurred? Most of my ` __exit__ `s are in this category.

---

<div class="post-metadata">

**Author:** ![iritkatriel](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/iritkatriel/32/2615_2.png) [@iritkatriel](https://discuss.python.org/u/iritkatriel)\
**Post date:** [March 7, 2023, 11:31am UTC](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402/21 "2023-03-07T11:31:36Z")

</div>

The compiler may not know that it’s an exit function:

```auto
>>> def myexit(self, *args): print(args)
...
>>> class CM:
... def __enter__ (self): return self
...
>>> CM. __exit__ = myexit
>>>
>>> with CM(): 1/0
...
(<class 'ZeroDivisionError'>, ZeroDivisionError('division by zero'), <traceback object at 0x102582d00>)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

```

[Next page](https://discuss.python.org/t/pep-707-a-simplified-signature-for-exit-and-aexit/24402.md?page=2)
