Python2 vs Python3 : Exception variable not defined

Question:

I wrote this small snippet in python2 (2.7.18) to catch the exception in a variable and it works

>>> ex = None
>>> try:
...   raise Exception("test")
... except Exception as ex:
...   print(ex)
...
test
>>> ex
Exception('test',)
>>>
>>> ex2 = None
>>> try:
...   raise Exception("test")
... except Exception as ex2:
...   print(ex2)
... finally:
...   print(ex2)
...
test
test

When I run the same in python3 (3.10.8), I get NameError

>>> ex = None
>>> try:
...   raise Exception("test")
... except Exception as ex:
...   print(ex)
...
test
>>> ex
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'ex' is not defined. Did you mean: 'hex'?
>>>
>>> ex2 = None
>>> try:
...   raise Exception("test")
... except Exception as ex2:
...   print(ex2)
... finally:
...   print(ex2)
...
test
Traceback (most recent call last):
  File "<stdin>", line 6, in <module>
NameError: name 'ex2' is not defined

What is the reason for this? Is the new python3 compiler doing optimisation where None assignment doesn’t mean anything or is the try/except clause doing some magic?

What is the workaround for this which works in both python2 and python3?

Asked By: likecs

||

Answers:

It’s not a magic really. It’s 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

And the reason:

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.

You can get the traceback object with ex.__traceback__.


What is the workaround for this which works in both python2 and
python3?

You have to explicitly assign it to a different name(something other than target in as target as it’s gonna get deleted):

exc_object = None

try:
    raise Exception("test")
except Exception as ex:
    print(ex)
    exc_object = ex

print(exc_object)

Note: This is only the case with except block and has nothing to do with as assignment statement as you can see in context managers: with...as... block for example.

Answered By: S.B