Python全局/局部变量

10

为什么这段代码有效:

var = 0

def func(num):
    print num
    var = 1
    if num != 0:
        func(num-1)

func(10)

但这个代码会报 "本地变量 'var' 在赋值前被引用" 的错误:

var = 0

def func(num):
    print num
    var = var
    if num != 0:
        func(num-1)

func(10)

请参考此答案及其评论,了解为什么会这样。 - Lauritz V. Thaulow
1
可能是Python作用域规则简述的重复问题。 - Martijn Pieters
6个回答

9

因为在第一段代码中,您创建了一个局部变量var并使用了它的值,而在第二段代码中,您正在使用局部变量var,但没有定义它。

因此,如果您想让您的第二个函数工作,您需要声明:

global var

在使用var之前的函数中。
def func(num):
    print num
    var = 1  <--  # You create a local variable
    if num != 0:
        func(num-1)

在这段代码中:

def func(num):
    print num
    var = var <--- # You are using the local variable on RHS without defining it
    if num != 0:
        func(num-1)

更新: -

然而,根据@Tim的评论,您不应在函数内部使用global变量。相反,在使用它之前定义您的变量,以在local scope中使用它。一般来说,您应该尝试将变量的作用域限制为local,甚至在local命名空间中也要limit局部变量的作用域,因为这样您的代码将更易于理解。

您增加变量作用域的程度越大,就越有可能被外部来源使用,而这并不需要使用它。


1
最好不要在程序中使用全局变量,或者干脆就不要用它们 :) - Tim Pietzcker
@TimPietzcker。好的。您能否具体说明一下原因? - Rohit Jain
@abarnert。谢谢 :) 我会查看链接的 :) - Rohit Jain
@RohitJain:是的,这适用于大多数编程语言,包括但不限于Python。网上还有一些文章专门解释为什么全局变量在Python中不好,但基本上它们都重复了大部分相同的论点,再加上额外的Python特定问题,这也是OP提出问题的原因所在。 - abarnert
@abarnert。是的,因为Python有一种不同的使用全局变量的方式。再次感谢您提供的链接。非常有帮助。我已经将它保存为书签 :) - Rohit Jain
显示剩余5条评论

6
如果您在函数中的任何位置使用了var = ...,那么“var”这个名称将被视为整个函数的本地变量,而不管该赋值发生在哪里。这意味着函数中所有var的出现都将在本地作用域中解析,因此var = var右侧的表达式在赋值前引用了一个未初始化的变量var,导致了"referenced before assignment"错误。请注意,html标签已保留。

这也让我感到惊讶。我本以为func会成为var的闭包,而var = var这一行会创建一个函数局部变量var,并将其赋值为模块级别的var的值。你回答中的“在函数的任何地方”听起来像是魔法。这真的是实际原因吗? - Kirk Strauser
1
@KirkStrauser:这有点神奇。名称的作用域是静态的,与几乎所有其他内容不同。 - Wooble
1
@KirkStrauser:Python在你所想的那种意义上并没有变量;它有名称绑定。对于大多数简单情况,效果是相同的,但这意味着在理解差异之前,某些情况可能会令人困惑。特别是,如果你从Javascript学习了“动态语言中闭包的工作原理”的规则,你会被Python搞糊涂的。 - abarnert
@abarnert,实际上我是在Python中学到的这些。我熟悉名称绑定,“按对象引用传递”等等。只是我以前没见过这种情况,它并不像我预期的那样。 - Kirk Strauser
既然我已经读了更多,来扩展F.J的答案:如果一个变量被赋值在函数左侧时,它会在编译时添加到函数的局部变量列表(在 co_varnames 属性中)。这导致(对我来说!)有趣的情况,比如 def foo(): var = var无法工作,但是 def foo(): exec('var = var') 却非常好用。我认为这更像是优化通过的实现细节,而不是语言本身的特性。 - Kirk Strauser
好的,这不是一个实现细节,因为Python的任何有效实现(包括pypy、Jython和IronPython)都必须在定义它们的作用域中定义所有本地变量。使用exec时,变量在编译时不存在(或者说,它的“编译时间”是在exec运行时而不是在定义foo时)。你也可以通过类似的方式“规避”这个问题,比如def foo(): locals()['var'] = var,但它仍然不会使规则失效。 - abarnert

1

在不声明全局变量的情况下,你可以读取全局变量。但是如果要写入全局变量,你需要声明它为全局变量。


这不是楼主的问题。确实,他的第一个函数可能没有达到他的期望,但他问的是为什么第二个函数会引发异常。而且,“你可以在不声明全局变量的情况下读取它”正是让他感到困惑的地方:在这种情况下,他无法在不声明全局变量的情况下读取全局变量var,因为他有一个与之重名的本地变量。 - abarnert

0
在你的第二段代码中,你创建了一个局部变量在RHS,并且没有定义它,就将其赋值给LHS变量var,这是全局的(在函数外定义的变量被明确地视为全局)。
如果你的意图是在函数内部创建一个局部变量并将其赋值为全局变量的值,那么这个方法可以解决问题:
var = 0

def func(num):
    print num
    func.var = var # func.var is referring the local
                   # variable var inside the function func
    if num != 0:
        func(num-1)

func(10)

-1
def runscan(self):
    p = os.popen('LD_PRELOAD=/usr/libv4l/v4l1compat.so zbarcam
                /dev/video0','r')

def input(self):
    self.entryc.insert(END, code)

这个怎么样? 我想使用本地的 'code' 将条形码的结果插入到我的 Tkinter entryBox 中的下一个函数中。 谢谢。

这似乎是您在原始帖子中提出了一个新的/不相关的问题。请考虑将其作为一个新问题发布。 - Jake

-3
每个函数块都是一个本地作用域。如果你想要分配到全局变量,你需要明确地这样做:
var = 0

def func(num):
    print num
    global var
    var = 1
    if num != 0:
        func(num-1)

print var # 0
func(2)
print var # 1

1
这意味着OP的第一个例子也不应该起作用,但它确实起作用了。此外,您的示例甚至没有使用全局变量。 - abarnert
我甚至不知道你现在在读什么。OP的第一个例子并没有抛出异常 - 实际上,这正是他来这里问这个问题的原因。 - abarnert
问题是关于第二个例子,而不是第一个。第一个没有理由抛出任何异常... @RohitJain解释了为什么第二个例子会抛出异常。我仍然不确定您为什么认为这个答案意味着第一个例子不应该工作。 - Nisan.H
感觉你只是在试图得分,而不是提供有用的信息。你删除了一个评论并编辑了你的答案,使我的评论看起来毫无意义,但你的答案仍然没有解决原帖作者的困惑,也没有解释两个现有有用答案中缺少的任何内容。我不再在这里发表评论了。 - abarnert
第二个函数中OP的目的不明确:他可能试图读取全局变量,也可能试图设置它,或者两者都有。由于他在提问后没有提供任何其他输入,我们都不知道他想要实现什么。我假设他试图设置全局变量,而你则假设另一种情况。我编辑了答案以使其更清晰,并删除了我的第一个评论,因为它没有建设性。顺便说一下,整个评论线程也不是很建设性...所以让我们停止吧。 - Nisan.H

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接