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?
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.
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?
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.