6

I know that in general python only makes new scopes for classes, functions etc., but I'm confused by the as statement in a try/except block or context manager. Variables assigned inside the block are accessible outside it, which makes sense, but the variable bound with as itself is not.

So this fails:

try:
    raise RuntimeError()
except RuntimeError as error:
    pass

print(repr(error))

but this succeeds:

try:
    raise RuntimeError()
except RuntimeError as e:
    error = e

print(repr(error))

What's going on with the variable bound with as, and why don't normal python scoping rules apply? The PEP indicates that it's just a normally bound python variable, but that doesn't seem to be the case.

petezurich
  • 9,280
  • 9
  • 43
  • 57
JoshuaF
  • 1,124
  • 2
  • 9
  • 23
  • 2
    Normal scope rules do apply, but there is an additional "unbind" operation performed by the `except` clause when it completes. – chepner Feb 24 '22 at 21:01

2 Answers2

8

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.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
  • @wjandrea I had internalized a rule to suppress syntax highlighting for REPL sessions, because they aren't proper code; but I see the highlighting works just fine. Having the `>>>` and `...` in "keyword" colour looks perfectly acceptable, yeah. – Karl Knechtel Feb 24 '22 at 21:11
  • 1
    Yeah, you might have learned that before SE switched to highlight.js. The new `lang-python` syntax handles prompts just fine, although [we still don't have support for the full `lang-python-repl`](https://meta.stackexchange.com/q/355221/343832). – wjandrea Feb 24 '22 at 21:18
  • 1
    Would btw be more usable without all those `...`, then someone wanting to try this wouldn't have to remove them. Python 3.10 IDLE does that. – Kelly Bundy Feb 25 '22 at 06:11
  • The code here isn't meant to be used; it's just demonstrating my claim. Code without the `...` would either appear misindented due to the `>>>`, or be awkwardly indented after copying and pasting. Removing the first four characters of each line in a block is easy with some text editors, including the one I use. – Karl Knechtel Mar 01 '22 at 01:44
6

This is a documented exception to the normal rules that *specifically applies to try-except statements, 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.

As noted, the reason this occurs to prevent reference cycles.

Note, this only applies to a try - except compound statement, as is not an independent statement, it is a part of different, independent compound statements (with and try-except)

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172