Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Why is Python throwing a NameError when doing nested exceptions with same variable name?

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

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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 as target, it is cleared at
the end of the except clause. 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 the except clause. 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
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading