【问题标题】:Can anyone help me understand Python variable scoping?谁能帮我理解 Python 变量范围?
【发布时间】:2011-08-12 22:49:48
【问题描述】:

我写了一个如下所示的测试程序:

#!/usr/bin/python

def incrementc():
    c = c + 1

def main():
    c = 5
    incrementc()

main()

print c

我认为既然我在 main 的主体中调用了 incrementc,那么 main 中的所有变量都会传递给 incrementc。但是当我运行这个程序时,我得到了

Traceback (most recent call last):
  File "test.py", line 10, in <module>
    main()
  File "test.py", line 8, in main
    incrementc()
  File "test.py", line 4, in incrementc
    c = c + 1
UnboundLocalError: local variable 'c' referenced before assignment

为什么 c 没有通过?如果我想让一个变量被多个函数引用,我是否必须全局声明它?我在某处读到全局变量不好。

谢谢!

【问题讨论】:

  • 全局变量绝对不错,如果使用得当,它不应该成为测试程序的一个大问题:)
  • 你不是第一个[问这个问题][1]的人。 [1]:stackoverflow.com/questions/423379/…

标签: python function


【解决方案1】:

您正在考虑dynamic scoping。动态作用域的问题在于incrementc 的行为将取决于之前的函数调用,这使得推理代码变得非常困难。相反,大多数编程语言(也包括 Python)使用静态作用域:c 仅在 main 内可见。

要完成您想要的,您可以使用全局变量,或者更好的是,将c 作为参数传递。现在,因为 Python 中的原语是不可变的,所以传递一个整数是不能改变的(它实际上是按值传递的),所以你必须将它打包到一个容器中,比如一个列表。像这样:

def increment(l):
    l[0] = l[0] + 1

def main():
    c = [5]
    increment(c)
    print c[0]

main()

或者,更简单:

def increment(l):
    return l + 1

def main():
    c = 5
    print increment(c)

main()

通常,全局变量不好,因为它们使编写难以理解的代码变得非常容易。如果您只有这两个功能,您可以继续将c 设为全局,因为代码的作用仍然很明显。如果您有更多代码,最好将变量作为参数传递;这样您可以更轻松地查看谁依赖于全局变量。

【讨论】:

  • 您忘记了在不同范围内重用变量的最重要方法——使用类/对象并操作属性。
  • 谢谢 :) 有趣的是列表和数字的传递方式不同。
  • @badatmath 他们没有通过不同的方式。只是你从来没有在这里修改l,只是l[0],所以你指的是局部变量。由于lc 指向同一个对象,修改l[0] 使c 反映变化。有关更多信息,请参阅我对stackoverflow.com/questions/7046971/… 的回答(python 标签中您之前的问题)
  • 甚至不知道存在动态作用域(从未使用过 perl 或使用它的 lisp 版本/方言) - 为什么有人想要使用它?听起来很糟糕。
  • Lisp 的第一个版本使用动态范围,因为它很容易实现。后来才发现这有多头疼。
【解决方案2】:

当一个变量在作用域中被赋值时,Python 假定它是整个作用域的局部变量,除非你另有说明。

因此,要使其按您的想法工作,您需要使用两个 global 语句:

#!/usr/bin/python
def incrementc():
    global c
    c = c + 1
def main():
    global c
    c = 5
    incrementc()
main()
print c

否则,在这两种情况下,您都在谈论名为 c 的局部变量。

但是,解决此问题的常规方法涉及globals。

#!/usr/bin/python
def incrementc(c):
    c = c + 1
    return c
def main():
    c = 5
    c = incrementc(c)
    return c
c = main()
print c

这里,在每个函数和全局范围内,c 指的是一个不同的变量,您将其作为参数传递并带有返回值。如果您想要只有一个c,请使用一个类:

class Foo:
    def __init__(self, c):
        self.c = c
        self.incrementc()
    def incrementc(self):
        self.c = self.c + 1


foo = Foo(5)
print foo.c

【讨论】:

    【解决方案3】:

    变量 c 没有通过,因为您没有将任何对 c 的引用交给函数 incrementc。

    您在这里看到的是 3 个作用域,即全局作用域以及函数 main 和 incrementc 中的作用域。在 main 中,您已经正确定义了一个变量 c,但是增量 c 对此一无所知 - 所以尝试增加它会失败。即使这两个函数成功了,尝试打印 c 也会在全局范围内失败,因为它不知道您在 main 中定义的 c。

    您有几个选择。一种方法:

    def incrementc(c):
        c = c + 1
        return c
    
    def main():
        c = 5
        c = incrementc(c)
        return c
    
    c = main()
    print c
    

    注意 c 是如何被传递的。当然名字也不必保留,你可以这样写:

    def increment(z):
        z = z + 1
        return z
    
    def main():
        bar = 5
        bar = increment(bar)
        return bar
    
    foo = main()
    print foo
    

    许多人可能不喜欢(有充分理由)的另一个选择是使用全局变量。在这种情况下:

    def incrementc():
        global c # indicate intention to write to this global, not just read it
        c = c + 1 
    
    def main():
        global c # declares c in global space
        c = 5
        incrementc()
    
    main()
    print c
    

    您希望在其中修改 c 的 GLOBAL 实例的任何函数,您需要通知该函数。所以你说,'全球c'。您可以在不这样做的情况下从全局读取。如果您决定在函数的本地空间中使用一个值,这将确保(在某种程度上)您不会犯错误并无意中用相似的名称覆盖全局空间中的值。

    希望这已经足够清楚,但请随时要求澄清任何一点(如果我错误地描述了其中的任何部分,我也愿意得到纠正)。

    【讨论】:

    • @badatmath 您只需在global 语句之后使用它就可以在全局空间中声明它。 c = 0 是不必要的,这与 Python 中的处理方式相反。
    • @agf 我可以在 任何函数 中使用 global 语句并且仍然让它成为全局变量吗?
    • 是的。无论您在哪里使用 global c,您都将在该范围内使用 c 作为全局变量,无论它是否在其他范围内用作全局变量。
    • @agf 你能详细说明它与 python 风格的不同之处吗?我认为,如果只是让用户清楚地了解全球范围内存在的一切,这似乎是一种很好的做法。我知道您不会在 python 中预先分配变量,但样式指南不是建议在模块顶部声明全局变量吗?
    • 如果您希望在函数运行之前将其初始化为零(例如可以先运行增量函数),那么有必要在 某处 (但它不应该是全局的),但在这种情况下不需要。因此,您的代码行对代码没有影响,仅充当注释。一行代码既可以做某事又可以自我注释是可以的,但如果它什么都不做,你应该使用注释来代替,即# Note: 'c' is a global variable!。这是 Python 设计为仅用作常量的全局变量的另一个示例。
    【解决方案4】:

    全局变量不好。

    就像朋友和敌人一样。让你的朋友靠近,但让你的敌人更靠近。

    函数main最后一个局部变量c,赋值为5 然后调用函数 inc..C。 main 中的 c 现在超出范围,因此您尝试使用不在范围内的 c 值 - 因此出现错误。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-08-05
      • 1970-01-01
      • 2014-03-10
      • 1970-01-01
      • 2011-06-12
      • 2020-10-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多