-
Notifications
You must be signed in to change notification settings - Fork 53
Description
I went over the __exit__ functions in the stdlib and the vast majority take *args as input and do nothing with it. So every time an __exit__ is called, the interpreter either pushes 3 Nones to the stack and calls a 3-arg method, or (if there is an exception) constructs the (exc, val tb) from the exception, pushes the 3 values to the stack and then calls the 3-arg method. If __exit__ took just the exception object, the interpreter would not need to construct these tuples, and it would just call a single-arg function, which is faster.
Microbenchmarks showed this makes entering and exiting a context manager about 13% faster when no exception is raised.
If this was just about performance I probably wouldn't be that bothered. But the current __exit__ API is a wart in the language that confuses programmers, particularly beginners. As of Python 3.11 it is completely redundant, and it would be nice to tidy this up.
In this branch, I made the interpreter introspect __exit__ and apply the single-arg API if it is either a method with co_argcount=2 or a C Function with flags == METH_O. In all other cases it applies the 3-arg api.
I added a bytecode for the no-exception exit case (previously we pushed 3 Nones and called __exit__, but now we don't know at compile time how many Nones we will need).
Benchmark results:
-
Just adding the new bytecode had no impact.
-
Also introspecting the function in each
__exit__call was 1% slower. This can be mitigated by: (*) specialising so that introspection is done only once. (2) we will get some of it back once the stdlib's__exit__s are rewritten to take one arg.