I ran into a case with nested try/except statements where I do not understand the behaviour of the Python interpreter.
An equivalent example to my usecase (connection errors) is in the minimal, reproducible example below:
try:
1 / 0
except ZeroDivisionError as error:
try:
[1, 2, 3][3]
except IndexError as error:
pass
print("The last error was:", error)
This example has the following output
Traceback (most recent call last):
File "/home/sorenmulli/lala.py", line 2, in <module>
1 / 0
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/sorenmulli/lala.py", line 8, in <module>
print("The last error was:", error)
NameError: name 'error' is not defined
where I would expect it to to output
The last error was: list index out of range.
I can get the expected behaviour by changing the code to
try:
1 / 0
except ZeroDivisionError as error_tmp:
error = error_tmp
try:
[1, 2, 3][3]
except IndexError as error_tmp:
error = error_tmp
print("The last error was:", error)
but this looks strange in my opinion: Why is this necessary? Shouldn’t the second assignment to error either overwrite the first or not: Why does it delete the variable completely?
- I am using Python 3.9.16, but I think the output is consistent across Python 3 versions.
>Solution :
This behaviour is documented here.
When an exception has been assigned using
astarget, it is cleared at
the end of theexceptclause. This is as if
except E as N:
foo
was translated to
except E as N:
try:
foo
finally:
del N
This means the exception must be assigned to a different name to be
able to refer to it after theexceptclause. Exceptions are cleared
because with the traceback attached to them, they form a reference
cycle with the stack frame, keeping all locals in that frame alive
until the next garbage collection occurs.
So in your code the error will be cleared at the end of except IndexError as error block.
try:
1 / 0
except ZeroDivisionError as error:
try:
[1, 2, 3][3]
except IndexError as error:
pass # `error` will be cleared here