Why does Python 3 raise a NameError
here? The name error
is defined in the first line, and assigned to in the try...except
block. Is this a bug in the interpreter, or am I missing a subtle change in the language definition from Python 2 to 3?
error = None
try:
raise Exception('Boom!')
except Exception as error:
pass
if error is not None:
raise error
This is the traceback when executed with Python 3.6.7:
$ python3 nameerror.py
Traceback (most recent call last):
File "nameerror.py", line 8, in <module>
if error is not None:
NameError: name 'error' is not defined
With Python 2.7.15, we get the expected Boom!
:
$ python2 nameerror.py
Traceback (most recent call last):
File "nameerror.py", line 9, in <module>
raise error
Exception: Boom!
If the code is wrapped in a function, Python 3.6.7 raises UnboundLocalError
instead, while Python 2.7.15 still works as expected.
$ python3 unbound.py
Traceback (most recent call last):
File "unbound.py", line 13, in <module>
main()
File "unbound.py", line 9, in main
if error is not None:
UnboundLocalError: local variable 'error' referenced before assignment
Curiously, removing the as error
from the exception handler fixes the NameError
resp. UnboundLocalError
.
This was an intentional change in the except
semantics to resolve an issue wherein reference cycles were formed between frames in a traceback and the exception in the frame:
In order to resolve the garbage collection issue related to PEP 344, except statements in Python 3 will generate additional bytecode to delete the target, thus eliminating the reference cycle. The source-to-source translation, as suggested by Phillip J. Eby [9], is
try:
try_body
except E as N:
except_body
...
gets translated to (in Python 2.5 terms)
try:
try_body
except E, N:
try:
except_body
finally:
N = None
del N
...
You can preserve the original exception simply by assigning it to some other name, e.g.:
try:
raise Exception('Boom!')
except Exception as error:
saved_error = error # Use saved_error outside the block