非直观的UnboundLocalError行为原因

5 投票
3 回答
662 浏览
提问于 2025-04-15 13:12

注意:这里有一个非常相似的问题 在这里。不过请耐心听我说;我的问题不是“为什么会出现这个错误”,而是“为什么Python会这样设计,以至于在这种情况下会抛出错误”。

我刚遇到了一些事情:

a = 5
def x()
    print a
    a = 6
x()

这会抛出一个 UnboundLocalException。我知道为什么会这样(在这个作用域的后面,a 被绑定了,所以在整个作用域内,a 被视为局部变量)。

在这种情况下:

a = 5
def x()
    print b
    b = 6
x()

这很有道理。但是第一个案例有一种直观的逻辑,意思是:

a = 5
def x()
    print globals()["a"]
    a = 6 # local assignment
x()

我想“直观”的版本之所以不被允许是有原因的,但是什么呢?虽然这可能是“明确优于隐含”的情况,但我总觉得玩弄 globals() 有点不太干净。

为了让大家更明白,我遇到这个问题的实际情况是我需要临时修改别人的脚本。在我(短暂的)修改中,我在脚本运行时进行了文件重命名,所以我插入了

import os
os.rename("foo", "bar")

到脚本中。这次插入发生在一个函数内部。模块在顶层已经导入了 os(我没有检查这一点),并且在我的插入之前,函数内部调用了一些 os.somefunction。这些调用显然触发了 UnboundLocalException

那么,有人能给我解释一下这种实现背后的原因吗?这是为了防止用户犯错吗?“直观”的方式会让字节码编译器变得更复杂吗?还是说有我没有想到的可能的歧义?

3 个回答

0

这其实是作用域的一个基本副作用。Python的开发者决定全局变量在你尝试使用的作用域中是不可用的。举个例子:

a = 5
def x():
    a = 6
    print a
x()
print a

这个例子输出的是 6 5

总的来说,使用全局变量被认为是一个不好的做法,所以Python的开发者对它进行了限制。你必须明确地让全局变量可以被访问,才能使用它。这实际上是为了避免混淆。想象一下:

a = 5
def x():
    a = 6
    print a
    y()
def y():
    global a
    a = a + 1
    print a
x()
print a

如果 x()a 当作局部变量来处理,并进行了赋值,那么输出就会是 6 6 7。写 x() 的人可能没有考虑到 y() 会使用一个叫 a 的全局变量。这就可能导致 y() 的行为变得异常。幸运的是,Python的作用域规则让 x() 的开发者不需要担心 y() 的开发者是怎么实现 y() 的,只需要确保它能正常工作。因此,这个例子输出的是 6 6 6(如预期那样)。

因此,UnboundLocalException 是非常直观的。

1

我觉得这里可能有点模糊。

a = 5
def x():
    print a
    a = 6  # could be local or trying to update the global variable
x()

这可能正如你所想的那样:

a = 5
def x():
    print globals()["a"]
    a = 6 # local assignment
x()

或者他们可能是想把全局变量更新为6:

a = 5
def x():
    global a
    print a
    a = 6
x()
6

在同一段线性代码中,使用完全相同的名字来指代完全不同的变量,这种复杂性真是让人头疼。想想看:

def aaaargh(alist):
  for x in alist:
    print a
    a = 23

你希望这段代码在你想要的Python版本中做什么呢?在循环的第一轮和第二轮中,print语句里的a竟然指向完全不同且无关的变量(假设真的有第二轮)?即使是只有一个元素的列表,它的工作方式也和不循环的代码不一样?说真的,这样下去简直是让人疯狂——光是想要记录和教这个,就可能让我想换一种编程语言。

那么,这种语言、它的开发者、老师、学习者和使用者,为什么要承受这么大的概念负担呢——去支持和鼓励半隐蔽、不明确使用全局变量?!这似乎并不是一个值得追求的目标,对吧?!

撰写回答