`except` 语句或上下文管理器中的 `as` 绑定的范围是什么?

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

我知道通常 python 只会为 类、函数等创建新的作用域,但我对 try/except 中的 as 语句感到困惑块或上下文管理器。在块内分配的变量可以在块外访问,这是有道理的,但与 as 本身绑定的变量不是。

所以这失败了:

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

print(repr(error))

但这成功了:

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

print(repr(error))

as 绑定的变量发生了什么,为什么不应用正常的 python 范围规则? PEP 表示它只是一个正常绑定的 python 变量,但事实并非如此。

这是*特别适用于 try-except 语句的正常规则的记录异常,来自 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.

如前所述,发生这种情况的原因是为了防止引用循环。

注意,这个适用于一个try - except复合语句,as不是独立语句,它是不同的独立复合语句的一部分语句(withtry-except

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 中所述。

相同的不适用于with块:

>>> 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()
>>> 

该行为特定于 except 块,不是 as 关键字。