This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Title: Simplify the signature of __exit__
Type: Stage:
Components: Versions:
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: AlexWaygood, JelleZijlstra, barry, iritkatriel, rhettinger
Priority: normal Keywords:

Created on 2022-01-10 13:08 by JelleZijlstra, last changed 2022-04-11 14:59 by admin.

Messages (5)
msg410210 - (view) Author: Jelle Zijlstra (JelleZijlstra) * (Python committer) Date: 2022-01-10 13:08
With Irit's recent changes (bpo-45711), the (typ, exc, tb) tuple is now always redundant with just exc. As a result, `__exit__` methods now should only need to accept a single argument. If we were designing the context manager protocol from scratch, we could do:

class CM:
    def __enter__(self):
        return self
    def __exit__(self, exc: BaseException | None, /):
        if exc is not None:
           print("an exception occurred")

Instead of the current cumbersome three-parameter `__exit__`.

But we're not designing it from scratch, and we need to deal with lots of existing code with three-parameter `__exit__` methods.

One possible decision is that we keep the existing signature, because the cost of changing it is too high. However, this would needlessly complicate learning Python for all future new users.

Here's a possible migration approach:
- When the interpreter encounters a `with` statement, it does `getattr(cm, "__enable_single_parameter_exit__", False)` on the context manager. If this attribute exists and is truthy, `__exit__` is called with a single parameter; otherwise we keep the current behavior.
- After a few releases, we flip the default, so you have to explicitly set `__enable_single_parameter_exit__ = False` to keep three-parameter `__exit__`. Libraries that still wish to support Python 3.10 and older can use this.
- Eventually, after Python 3.10 reaches EOL, we always use one-parameter `__exit__`.

To help the migration, we can do a few things:
- Linters and type checkers can check for discrepancies in the __exit__ signature.
- In the interpreter, we can provide a custom error message if the number of parameters to `__exit__` is not as expected, nudging the user towards setting `__enable_single_parameter_exit__`.
- In the compiler, we can likely warn if a class body contains three-parameter `__exit__` but doesn't set `__enable_single_parameter_exit__ = False`, or similar discrepancies.

Everything here should apply equally to `__aexit__`.

This will require a PEP if implemented, but I want to first see whether other people think this is worth pursuing at all.
msg410211 - (view) Author: Irit Katriel (iritkatriel) * (Python committer) Date: 2022-01-10 13:22
I've outlined a different approach here:

Basically, to add introspection capabilities in the C API so that the interpreter can find out whether __exit__ expects one or three args.
msg410213 - (view) Author: Jelle Zijlstra (JelleZijlstra) * (Python committer) Date: 2022-01-10 13:48
Thanks, that would work in normal cases but can cause issues if __exit__ takes *args. Still, that may be enough of an edge case that we can get it to work.
msg410214 - (view) Author: Irit Katriel (iritkatriel) * (Python committer) Date: 2022-01-10 13:55
If it takes *args we should assume that it expects the triplet rather than a single exception.  It's not an edge case, it's very common to write __exit__ like that.
msg410250 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2022-01-10 19:57
Implementation and transition issues aside, I like this idea.  People can mostly just use isinstance() checks to match one or more exception types.  Also, the single argument form would work well with structural pattern matching:

    def __exit__(self, exc):
        match exc:
            case IOError():

That said, there will be some cases that are worse off.  If the code actually needs "exctype", perhaps for logging or for an exact match, then the new single argument form will just shift complexity away from the signature and into the main body of the code.  This may be the common case.  During code reviews, I see the "exctype" argument used more frequently than "excinst".
Date User Action Args
2022-04-11 14:59:54adminsetgithub: 90488
2022-01-12 01:28:12barrysetnosy: + barry
2022-01-10 22:04:19AlexWaygoodsetnosy: + AlexWaygood
2022-01-10 19:57:23rhettingersetnosy: + rhettinger
messages: + msg410250
2022-01-10 13:55:59iritkatrielsetmessages: + msg410214
2022-01-10 13:48:43JelleZijlstrasetmessages: + msg410213
2022-01-10 13:22:41iritkatrielsetmessages: + msg410211
2022-01-10 13:08:08JelleZijlstracreate