Avoid `exc=None; del exc` in bytecode where unnecessary
See https://github.com/faster-cpython/ideas/issues/490.
To break reference cycles, the compiler has an implicit del e at the end of an exception handler. To make UnboundLocalErrors impossible during that deletion, the compiler adds e=None; del e instead.
However, the e=None part is unnecessary in many situations, namely, when we can prove that e is guaranteed to be bound (almost always!).
This can reduce the size of some bytecode objects. For example, the four <---- lines below can be deleted:
>>> def f():
... try: pass
... except Exception as e: pass
...
>>> dis(f)
1 0 RESUME 0
2 2 LOAD_CONST 0 (None)
4 RETURN_VALUE
6 PUSH_EXC_INFO
3 8 LOAD_GLOBAL 0 (Exception)
20 CHECK_EXC_MATCH
22 POP_JUMP_IF_FALSE 11 (to 46)
24 STORE_FAST 0 (e)
26 POP_EXCEPT
28 LOAD_CONST 0 (None) <---------
30 STORE_FAST 0 (e) <---------
32 DELETE_FAST 0 (e)
34 LOAD_CONST 0 (None)
36 RETURN_VALUE
38 LOAD_CONST 0 (None) <---------
40 STORE_FAST 0 (e) <---------
42 DELETE_FAST 0 (e)
44 RERAISE 1
>> 46 RERAISE 0
>> 48 COPY 3
50 POP_EXCEPT
52 RERAISE 1
ExceptionTable:
6 to 24 -> 48 [1] lasti
38 to 46 -> 48 [1] lasti
- PR: gh-99361
I believe e is always guaranteed to be bound, by construction. My assumption is that the None is assigned to break the cycle so that gc would have fewer cycles to deal with (exception tracebacks reference the frame, and this is a reference from the frame to the exception).
I guess benchmarking would tell.
I believe e is always guaranteed to be bound, by construction.
Not in the presence of an extra manually-added del e, right?
def f():
try:
1/0
except ZeroDivisionError as e:
del e
print(e)
f()
Traceback (most recent call last):
File "<pyshell#13>", line 3, in f
1/0
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<pyshell#14>", line 1, in <module>
f()
File "<pyshell#13>", line 6, in f
print(e)
UnboundLocalError: cannot access local variable 'e' where it is not associated with a value
It looks like the e = None; del e code was first added in issue https://github.com/python/cpython/issues/44437, commit https://github.com/python/cpython/commit/b940e113bf90ff71b0ef57414ea2beea9d2a4bc0.
The mailing list thread leading up to the issue is here: https://mail.python.org/pipermail/python-3000/2007-January/005384.html
- Folks are discussing avoiding reference cycles, potentially by using weakrefs
- Phillip J. Eby proposes always using
del e, Guido is +1 - Phillip J. Eby: """Actually, on second thought it occurs to me that the above code isn't a 100% correct translation, because if "body" contains its own
del e, then it will fail.""" - Ka-Ping Yee proposes
e = None - Phillip J. Eby proposes
e = None; del e, that what gets implemented. - The benefits outweigh this introduction of something like a "block scope"