18

I have the following code:

def foo():
    e = None
    try:
        raise Exception('I wish you would except me for who I am.')
    except Exception as e:
        print(e)
    print(e)

foo()

In Python 2.7, this runs as expected and prints:

I wish you would except me for who I am.
I wish you would except me for who I am.

However in Python 3.x, the first line is printed, but the second line is not. It seems to delete the variable in the enclosing scope, giving me the following traceback from the last print statement:

Traceback (most recent call last):
  File "python", line 9, in <module>
  File "python", line 7, in foo
UnboundLocalError: local variable 'e' referenced before assignment

It is almost as if a del e statement is inserted after the except block. Is there any reasoning for this sort of behavior? I could understand it if the Python developers wanted except blocks to have their own local scope, and not leak into the surrounding scope, but why must it delete a variable in the outer scope that was previously assigned?

Shashank
  • 13,713
  • 5
  • 37
  • 63

1 Answers1

23

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 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 is covered in these two PEPs.

  1. PEP 3110 - Catching Exceptions in Python 3000

  2. PEP 344 - Exception Chaining and Embedded Tracebacks

thefourtheye
  • 233,700
  • 52
  • 457
  • 497
  • 1
    Carrying the link from the deleted answer by @Barry: https://www.wefearchange.org/2013/04/python-3-language-gotcha-and-short.html – Mr_and_Mrs_D Jan 19 '19 at 17:40