Search code examples
pythoncontextmanager

What is the scope of the `as` binding in an `except` statement or context manager?


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.


Solution

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