"除了Foo为bar"导致"bar"从范围中移除

kni*_*nap 42 python scoping python-3.x

给出以下代码:

msg = "test"
try:
    "a"[1]
except IndexError as msg:
    print("Error happened")
print(msg)
Run Code Online (Sandbox Code Playgroud)

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

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

Uku*_*kit 52

msgexcept子句中的范围msg与第一行的范围相同.

但是在Python 3中我们也有这种新行为:

使用时分配异常as target时,将在except子句的末尾清除它.这就好像

except E as N:
    foo
Run Code Online (Sandbox Code Playgroud)

被翻译成

except E as N:
    try:
        foo
    finally:
        del N
Run Code Online (Sandbox Code Playgroud)

这意味着必须将异常分配给不同的名称才能在except子句之后引用它.异常被清除,因为附加了回溯,它们与堆栈帧形成一个引用循环,使该帧中的所有本地生存,直到下一次垃圾收集发生.

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

  • 这特别回答了在`except:`之后如何以及为什么删除之前的`msg`的问题,包括对文档的引用,所以恕我直言应该是接受的答案. (11认同)

blh*_*ing 34

是的,一旦引发异常并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")
Run Code Online (Sandbox Code Playgroud)

这输出:

object deleted
Error happened
exception deleted
NameError: name 'msg' is not defined
Run Code Online (Sandbox Code Playgroud)

  • @Shamtam正如贾科莫解释的那样,它绝对是*不稳定的.Python工作的方式,它重新绑定现有范围*中的`msg`*.但是`msg`的范围存在于`try`块之外(如果没有引发异常,将会继续).*运行时条件*确定变量范围(应该是静态的)这一事实是类型系统的粗暴颠覆.由于Python是动态类型的,这当然有效,但它仍然很奇怪.更重要的是,Python 2完全不同地处理这个问题,并且正如预期的那样. (31认同)
  • @Shamtam"这是Python的工作方式":这肯定是正确的.没有人否认这一点.但这并不意味着人们不应该认为Python不稳定."其他流行语言"中的范围规则允许使用这些语言的程序员避免在他们到达except块(或类似)之前跟踪他们使用的名称的精神负担,以便能够避免使用现有名称块; 解释器或编译器为它们做了. (11认同)
  • 在`except`块的末尾删除`msg`的原因与范围无关.他们希望清除在Python 3中添加`__traceback__`属性创建的引用循环.参见[PEP 3110](https://www.python.org/dev/peps/pep-3110/#semantic-changes) .这绝对令人困惑,并且有一个强烈的论据要求Python应该停止尝试支持关闭GC运行,但这不是范围问题. (10认同)
  • 这根本不是什么好事.通过使用`except ... as msg`,你将`msg`绑定到异常,这将删除对原始对象的所有引用.相反,如果使用`除了...作为m`,它不会导致任何问题,因为在except块中没有修改原始绑定`msg`. (6认同)
  • @LightnessRacesinOrbit是的,这绝对是奇怪的.我的意思是:如果你想要一个新的范围,那么创建一个新的范围,不要杀死对第一个`msg`的引用.如果您不想要新范围,那么为什么要杀死异常引用? (5认同)
  • @Shamtam python2行为是**我期望的**.如果`except`没有创建一个新的范围,旧的`msg`*应该在重新分配给异常*后失去,**但是没有理由删除异常**.我们可以很容易地用`for`循环做同样的事情:在`for`(或附加到它的`else`块)的末尾添加一个隐藏的`del <vars>`.与`with`相同:`用x作为:......; 打印(X)`.他们决定添加这个隐藏的`del`,毫无理由地打破了python已经拥有的所有约定.你这样说是"因为它是用标准写的",这是正确的,但...... (5认同)
  • @Shamtam有什么区别`msg = 1;尝试:...除了E作为msg:`和`x = 1;对于x的东西:...`?在`for`` x`的末尾保持了对最后一次迭代的绑定,我没有看到任何有理由在`except`块的末尾添加一个隐藏的`del msg`.Python**永远不会以这种方式工作,**所有其他语句都不能以这种方式工作**,以前版本的python在这种情况下不起作用.正如我所说的,从"词法范围"的角度来看,你要么引入一个新的范围(并且`msg`保留旧值),要么重新绑定名称.不应该以这种方式删除绑定 (4认同)
  • @hobbs try-catch没有引入新的范围,所以不会,在运行异常块之后,不再引用msg绑定的原始对象,因为msg被反弹然后被except块删除.正如@Konrad Rudolph解释的那样,这就是Python的工作方式.这与`x = A()\ x = B()\ del x\print x`的想法相同.Python2中的行为与此不同,但仍然是"意外".而不是抛出`NameError`,你只需要`字符串索引超出范围`.Py2和Py3之间的区别在于Py2中的异常没有被删除(参见Uku Loskit的回答). (3认同)
  • 在Python中描述其他语言中出乎意料的行为时,这是使用术语"绑定"而不是"变量"的语义的一个很好的理由. (2认同)
  • @Shamtam你没有给出任何理由,为什么做出这个决定,因为它肯定打破了最不惊讶的规则.也许有一个理由为什么在`except`块之外访问异常对象不是一个好主意,但是你写的没有给出任何关于为什么应该是这种情况的提示. (2认同)
  • 但是为什么消息是"Name*msg*not defined",而它只是一个被引用的对象变得无法访问?姓名*msg*仍然是定义的,不是吗? (2认同)

Mic*_*ael 7

异常块删除块末尾的catch变量,但它们没有自己的作用域.所以事件的顺序如下:

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

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

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

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