当前位置:  开发笔记 > 编程语言 > 正文

Python变量范围错误

如何解决《Python变量范围错误》经验,为你挑选了6个好方法。

以下代码在Python 2.5和3.0中按预期工作:

a, b, c = (1, 2, 3)

print(a, b, c)

def test():
    print(a)
    print(b)
    print(c)    # (A)
    #c+=1       # (B)
test()

但是,当我取消注释行(B)时,我得到了UnboundLocalError: 'c' not assigned一行(A).的值ab被正确地打印.这让我感到困惑,原因有两个:

    为什么在行(A)处抛出运行时错误,因为后面的行(B)语句?

    为什么变量ab打印符合预期,同时c引发错误?

我能想到的唯一解释是,赋值创建了一个局部变量,即使在创建局部变量之前,它也优先于"全局"变量.当然,变量在存在之前"窃取"范围是没有意义的.cc+=1c

有人可以解释一下这种行为吗?



1> recursive..:

Python会根据您是否从函数中为它们赋值来不同地处理函数中的变量.如果函数包含对变量的任何赋值,则默认情况下将其视为局部变量.因此,当您取消注释该行时,您将尝试在为其分配任何值之前引用局部变量.

如果您希望变量c引用全局cput

global c

作为功​​能的第一行.

至于python 3,现在有

nonlocal c

您可以用来引用具有c = 3变量的最近的封闭函数范围.


变量范围决策由编译器决定,编译器通常在您第一次启动程序时运行一次.但是,值得记住的是,如果程序中包含"eval"或"exec"语句,编译器也可能会稍后运行.
@brainfsck:如果你区分"查找"和"分配"变量,最容易理解.如果在当前范围中找不到该名称,则查找会回退到更高的范围.赋值始终在本地范围内完成(除非您使用`global`或`nonlocal`强制全局或非本地赋值)
谢谢.快问.这是否意味着Python在运行程序之前决定每个变量的范围?在运行功能之前?
好的谢谢你.我猜"解释语言"并不像我想象的那么多.

2> Charlie Mart..:

Python有点奇怪,它将所有内容保存在各种范围的字典中.原始的a,b,c位于最上面的范围内,因此位于最上面的字典中.该函数有自己的字典.当你到达print(a)print(b)语句时,字典中没有任何名称,所以Python查找列表并在全局字典中找到它们.

现在我们到达c+=1,当然,相当于c=c+1.当Python扫描该行时,它会显示"aha,有一个名为c的变量,我会将它放入我的本地范围字典中." 然后,当它为赋值右侧的c寻找c的值时,它会找到名为c的局部变量,该变量尚无值,因此抛出错误.

global c上面提到的语句只是告诉解析器它使用c了全局范围,因此不需要新的范围.

它之所以说它出现问题的原因是因为它在尝试生成代码之前有效地寻找名称,因此在某种意义上它并不认为它确实在做那条线.我认为这是一个可用性错误,但通常一个好习惯就是学会不要认真地对待编译器的消息.

如果有任何安慰,我可能花了一天时间挖掘并尝试同样的问题,然后才发现Guido写的关于解释一切的词典.

更新,看评论:

它不会扫描代码两次,但它会分两个阶段扫描代码,lexing和parsing.

考虑一下这行代码的解析是如何工作的.词法分析器读取源文本并将其分解为词汇,即语法的"最小组件".所以,当它击中线

c+=1

它把它分解成类似的东西

SYMBOL(c) OPERATOR(+=) DIGIT(1)

解析器最终想要将它变成一个解析树并执行它,但由于它是一个赋值,在它之前,它会在本地字典中查找名称c,看不到它,并将其插入字典中,标记它没有初始化.在完全编译的语言中,它只会进入符号表并等待解析,但由于它不会有第二次传递的奢侈,因此词法分析器会做一些额外的工作以使以后的生活更轻松.只有,然后它才会看到操作者,看到规则说"如果你有一个操作员+ =左手边必须已经初始化"并说"哎呀!"

这里的要点是它还没有真正开始解析该行.这一切都发生在实际解析的准备上,所以行计数器还没有前进到下一行.因此,当它发出错误信号时,它仍然认为它在前一行.

正如我所说,你可以说这是一个可用性错误,但它实际上是一个相当普遍的事情.有些编译器对此更加诚实,并说"XXX线上或附近有错误",但这个没有.


@CharlieMartin很好的回答和btw +1为你的个人资料评论马丁先生!让我的一天:D
关于实现细节的注意事项:在CPython中,本地范围通常不作为`dict`处理,它在内部只是一个数组(`locals()`将填充一个`dict`返回,但是对它的更改不会创建新的"当地人").解析阶段是查找对本地的每个赋值,并从该名称转换到该数组中的位置,并在引用该名称时使用该位置.在进入函数时,非参数局部化初始化为占位符,并且当读取变量且其关联索引仍具有占位符值时,会发生"UnboundLocalError".

3> Brian..:

看一下反汇编可能会澄清发生了什么:

>>> def f():
...    print a
...    print b
...    a = 1

>>> import dis
>>> dis.dis(f)

  2           0 LOAD_FAST                0 (a)
              3 PRINT_ITEM
              4 PRINT_NEWLINE

  3           5 LOAD_GLOBAL              0 (b)
              8 PRINT_ITEM
              9 PRINT_NEWLINE

  4          10 LOAD_CONST               1 (1)
             13 STORE_FAST               0 (a)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

如您所见,用于访问a的字节码是LOAD_FAST和b LOAD_GLOBAL.这是因为编译器已经识别出在函数内分配了a,并将其归类为局部变量.本地化的访问机制对于全局变量是根本不同的 - 它们在帧的变量表中静态分配了一个偏移量,这意味着查找是一个快速索引,而不是像全局变量那样更昂贵的dict查找.正因为如此,Python正在读取该print a行"获取局部变量的值'a'保存在插槽0中并打印它",并且当它检测到该变量仍然未初始化时,引发异常.


我以前没见过dis模块.有趣的东西 - 谢谢你:-)

4> Mongoose..:

当您尝试传统的全局变量语义时,Python具有相当有趣的行为.我不记得细节,但是你可以很好地读取在'global'范围内声明的变量的值,但如果你想修改它,你必须使用global关键字.尝试更改test()为:

def test():
    global c
    print(a)
    print(b)
    print(c)    # (A)
    c+=1        # (B)

此外,您收到此错误的原因是因为您还可以在该函数内声明一个与"全局"同名的新变量,它将完全独立.解释器认为您正在尝试在此作用域中创建一个新变量,c并在一个操作中对其进行全部修改,这在Python中是不允许的,因为这个新的c未初始化.



5> Sahil kalra..:

明确的最好例子是:

bar = 42
def foo():
    print bar
    if False:
        bar = 0

在调用时foo(),这也会引发 UnboundLocalError虽然我们永远不会到达行bar=0,所以永远不应该创建逻辑局部变量.

神秘之处在于" Python是一种解释语言 ",函数的声明foo被解释为单个语句(即复合语句),它只是笨拙地解释它并创建局部和全局范围.因此bar在执行前在本地范围内被识别.

有关此类更多示例,请阅读以下文章:http://blog.amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2/

这篇文章提供了Python变量的范围的完整描述和分析:



6> mcdon..:

这里有两个可能有用的链接

1:docs.python.org/3.1/faq/programming.html?highlight=nonlocal#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value

2:docs.python.org/3.1/faq/programming.html?highlight=nonlocal#how-do-i-write-a-function-with-output-parameters-call-by-reference

链接一描述错误UnboundLocalError.链接二可以帮助重写您的测试功能.根据链接二,原始问题可以改写为:

>>> a, b, c = (1, 2, 3)
>>> print (a, b, c)
(1, 2, 3)
>>> def test (a, b, c):
...     print (a)
...     print (b)
...     print (c)
...     c += 1
...     return a, b, c
...
>>> a, b, c = test (a, b, c)
1
2
3
>>> print (a, b ,c)
(1, 2, 4)

推荐阅读
勤奋的瞌睡猪_715
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有