Name binding in `except` clause deleted after the clause
No this is not a bug. The behavior you are experiencing is clearly and explicitly defined in the Python 3 documentation for the try
/except
statement. The reason for this behavior is also given:
When an exception has been assigned using
as target
, it is cleared at the end of theexcept
clause. This is as ifexcept 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.
The reason declaring the name outside of the scope of the try
/except
block didn't work is because you used exc
in the as
clause. So that was the name Python deleted.
The fix is to use a different name in the as
clause to bind the exception to, and then assign the global variable to the different exception name:
>>> exc_global = None
>>> try:
1 / 0
text_template = "All fine!"
except ZeroDivisionError as exc:
exc_global = exc
text_template = "Got exception: {exc.__class__.__name__}"
>>> print(text_template.format(exc=exc_global))
Got exception: ZeroDivisionError
As Anthony Sottile noted in the comments, the disassembly for the try
/except
code also clearly supports the above statements made by the documentation:
>>> code = """
try:
1/0
text_template = "All fine!"
except ZeroDivisionError as exc:
text_template = "Got exception: {exc.__class__.__name__}"
"""
>>> from dis import dis
>>> dis(code)
2 0 SETUP_EXCEPT 16 (to 18)
3 2 LOAD_CONST 0 (1)
4 LOAD_CONST 1 (0)
6 BINARY_TRUE_DIVIDE
8 POP_TOP
4 10 LOAD_CONST 2 ('All fine!')
12 STORE_NAME 0 (text_template)
14 POP_BLOCK
16 JUMP_FORWARD 38 (to 56)
5 >> 18 DUP_TOP
20 LOAD_NAME 1 (ZeroDivisionError)
22 COMPARE_OP 10 (exception match)
24 POP_JUMP_IF_FALSE 54
26 POP_TOP
28 STORE_NAME 2 (exc)
30 POP_TOP
32 SETUP_FINALLY 10 (to 44)
6 34 LOAD_CONST 3 ('Got exception: {exc.__class__.__name__}')
36 STORE_NAME 0 (text_template)
38 POP_BLOCK
40 POP_EXCEPT
42 LOAD_CONST 4 (None)
>> 44 LOAD_CONST 4 (None)
46 STORE_NAME 2 (exc)
48 DELETE_NAME 2 (exc)
50 END_FINALLY
52 JUMP_FORWARD 2 (to 56)
>> 54 END_FINALLY
>> 56 LOAD_CONST 4 (None)
58 RETURN_VALUE
Python 3 exception deletes variable in enclosing scope for unknown reason
Quoting the documentation of try
,
When an exception has been assigned using
as target
, it is cleared at the end of the except clause. This is as ifexcept 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.
This is covered in these two PEPs.
PEP 3110 - Catching Exceptions in Python 3000
PEP 344 - Exception Chaining and Embedded Tracebacks
why variable used in except is removed
This is explained in the documentation (parts in [ ] braces added by me):
When an exception has been assigned using
as target
[i.e.as x
in your case], 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 [i.e. this name,
x
in your case] after the except clause.
The reason for this is also stated:
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.
why variable used in except is removed
This is explained in the documentation (parts in [ ] braces added by me):
When an exception has been assigned using
as target
[i.e.as x
in your case], 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 [i.e. this name,
x
in your case] after the except clause.
The reason for this is also stated:
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.
except Foo as bar causes bar to be removed from scope
msg
in the except clause is in the same scope as msg
on the first line.
But in Python 3 we have this new behavior too:
When an exception has been assigned using
as target
, it is cleared at
the end of the except clause. This is as ifexcept 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, you "overwrite msg
" in the exception handler, and exiting the handler will delete the variable to clear the traceback reference cycle.
Variable scoping with an except block: Difference between Python 2 and 3
This behaviour was introduced in Python 3, to prevent reference cycles, because the exception target - arithmetic_error
in the question - keeps a reference to the traceback.
From the Language Reference
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.
This was originally documented in PEP3110.
What is the scope of the `as` binding in an `except` statement or context manager?
As explained in PEP 3110, as well as current documentation, variables bound with as
in an except
block are explicitly and specially cleared at the end of the block, even though they share the same local scope. This improves the immediacy of garbage collection. The as
syntax was originally not available for exceptions in 2.x; it was backported for 2.6, but the old semantics were preserved.
The same does not apply to with
blocks:
>>> from contextlib import contextmanager
>>> @contextmanager
... def test():
... yield
...
>>> with test() as a:
... pass
...
>>> a # contains None; does not raise NameError
>>>
>>> def func(): # similarly within a function
... with test() as a:
... pass
... return a
...
>>> func()
>>>
The behaviour is specific to the except
block, not to the as
keyword.
Variable scope in case of an exception in python
Simple: while
does not create a scope in Python. Python has only the following scopes:
- function scope (may include closure variables)
- class scope (only while the class is being defined)
- global (module) scope
- comprehension/generator expression scope
So when you leave the while
loop, e
, being a local variable (if the loop is in a function) or a global variable (if not), is still available.
tl;dr: Python is not C.
Related Topics
Target Wsgi Script Cannot Be Loaded as Python Module
How to Find First Non-Zero Value in Every Column of a Numpy Array
How to Get 'Real-Time' Information Back from a Subprocess.Popen in Python (2.5)
Reading Dynamically Generated Web Pages Using Python
Why Is the Value of _Name_ Changing After Assignment to Sys.Modules[_Name_]
Unicodedecodeerror: 'Ascii' Codec Can't Decode Byte 0Xe2 in Position 13: Ordinal Not in Range(128)
Python Ftp Implicit Tls Connection Issue
Does a Slicing Operation Give Me a Deep or Shallow Copy
How to Find Out Whether a File Is at Its 'Eof'
How to Tell If a String Repeats Itself in Python
Comparing Numpy Arrays Containing Nan
Trailing Slash Triggers 404 in Flask Path Rule
Is It Pythonic to Import Inside Functions
Pandas Dataframe Stack Multiple Column Values into Single Column
Installing Numpy with Pip on Windows 10 for Python 3.7
How to Find Duplicate Elements in Array Using for Loop in Python