【问题标题】:"except Foo as bar" causes "bar" to be removed from scope [duplicate]“除了Foo as bar”导致“bar”从范围中删除[重复]
【发布时间】:2018-10-24 09:50:09
【问题描述】:

给定以下代码:

msg = "test"
try:
    "a"[1]
except IndexError as msg:
    print("Error happened")
print(msg)

有人可以解释为什么这会导致 Python 3 中出现以下输出吗?

Error happened
Traceback (most recent call last):
  File "test.py", line 6, in <module>
    print(msg)
NameError: name 'msg' is not defined

【问题讨论】:

    标签: python python-3.x scoping


    【解决方案1】:

    except 子句中的msg 与第一行的msg 在同一范围内。

    但是in Python 3 we have this new behavior too:

    当使用as target 分配异常时,它会在 except 子句的结尾。这好像

    except E as N:
        foo
    

    被翻译成

    except E as N:
        try:
            foo
        finally:
            del N
    

    这意味着必须将异常分配给不同的名称 可以在 except 子句之后引用它。异常被清除 因为带有附加到它们的回溯,它们形成了参考 与堆栈帧循环,使该帧中的所有本地人保持活动状态 直到下一次垃圾回收发生。

    所以,你在异常处理程序中“覆盖msg”,退出处理程序将删除变量以清除回溯引用循环。

    【讨论】:

    • 这专门回答了以前的msgexcept: 之后如何以及为什么被删除的问题,包括对文档的引用,所以恕我直言,它应该是公认的答案。
    【解决方案2】:

    是的,一旦引发异常并且为msg 分配了新的异常对象,原始对象就没有更多引用,因此将被删除。新的异常对象一离开except 块也会被删除。

    你可以通过重写对象的__del__方法和分配给msg的异常来验证:

    class A:
        def __del__(self):
            print('object deleted')
    class E(Exception):
        def __del__(self):
            print('exception deleted')
    msg = A()
    try:
        raise E()
    except E as msg:
        print("Error happened")
    

    这个输出:

    object deleted
    Error happened
    exception deleted
    NameError: name 'msg' is not defined
    

    【讨论】:

    • @LightnessRacesinOrbit 是的,这绝对很奇怪。我的意思是:如果你想要一个新的范围,然后创建一个新的范围,不要取消对第一个 msg 的引用。如果您不想要新的范围,那么为什么要终止异常引用?
    • 一点也不奇怪。通过使用except ... as msg,您将msg 绑定到异常,这将删除对原始对象的所有引用。相反,如果使用 except ... as m,则不会导致任何问题,因为原始绑定 msg 未在 except 块中修改。
    • @Shamtam 正如 Giacomo 解释的那样,这绝对很不稳定。 Python 的工作方式是重新绑定msg在现有范围内。但是msg 的范围存在于try 块之外(如果没有引发异常,它将继续存在)。 运行时条件 确定变量(应该是静态的)范围的事实是对类型系统的粗鲁颠覆。由于 Python 是动态类型的,这当然可以,但它仍然很奇怪。更重要的是,Python 2 以完全不同的方式处理这个问题,并且符合预期。
    • @Shamtam “这就是 Python 的工作方式”:这肯定是真的。没有人否认这一点。但这并不意味着人们不应该认为 Python 有问题。 “其他流行语言”中的范围规则允许使用这些语言的程序员避免在到达 except 块(或类似语言)之前跟踪他们使用过的名称的精神负担,从而能够避免在区块;解释器或编译器为他们做这件事。
    • msgexcept块末尾被删除的原因与作用域无关。他们想要清除在 Python 3 中添加 __traceback__ 属性所创建的引用循环。请参阅 PEP 3110。这绝对令人困惑,并且有一个强有力的论据表明 Python 应该停止尝试支持在关闭 GC 的情况下运行,但这不是范围问题。
    【解决方案3】:

    异常块在块的末尾删除捕获的变量,但它们没有自己的范围。所以事件的顺序是:

    1) msg 设置为本地范围内的某个字符串

    2) msg 设置为与 1 相同本地范围内的 IndexError 对象

    3) msg 在异常块结束时从本地范围中删除

    4) msg 不再定义在本地范围内,因此访问它的尝试失败

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-04
      • 2010-12-09
      • 2014-03-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多