Python:为什么说只有引用的变量是隐式全局的?

6 投票
4 回答
650 浏览
提问于 2025-04-18 05:20

在Python的常见问题解答中,我们可以看到:

在Python中,如果一个变量只在函数内部被引用,那么它会被默认为全局变量。

而在Python的函数定义教程中,我们又了解到:

执行一个函数时,会创建一个新的符号表,用于存放函数的局部变量。更具体地说,函数中的所有变量赋值都会把值存储在这个局部符号表里;而当引用变量时,Python会先在局部符号表中查找,如果找不到,就会去外层函数的局部符号表查找,再找全局符号表,最后查找内置名称的表。

我对教程中的内容理解得很清楚,但说“只在函数内部引用的变量被默认为全局变量”这句话对我来说有点模糊。

为什么说它们是默认为全局的呢?如果我们实际上是先查看局部符号表,然后再查找更“广泛”的符号表?这是不是意味着,如果你只在函数内部引用一个变量,就不需要担心它是局部的还是全局的?

4 个回答

1

跟其他一些编程语言不一样,Python 在查找变量名时,不会先在本地的符号表里找,如果找不到再去更大的范围里找。Python 会在编译时就确定一个变量是本地的,而不是在运行时。只要这个变量被赋值过(包括作为参数传入),它就被认为是本地变量。如果一个名字没有被赋值过,也没有明确声明为全局变量,那么它就被认为是全局变量,只会在全局命名空间里查找。这种做法让 Python 能够优化本地变量的访问(使用 LOAD_FAST 字节码),所以本地变量的访问速度更快。

关于闭包(在 Python 3 中还有 nonlocal 的情况)会有一些复杂的地方,但大体上就是这样。

1

这段话有点让人困惑,文档的说明也可以更清楚一些。

在这里,“引用”指的是一个名字并没有被赋值,而只是被读取。例如,a = 1 是把值1赋给了变量a,而 print(a)(这是Python 3的写法)则是引用了变量a,并没有进行赋值。

如果你像上面那样引用变量a而没有赋值,Python解释器会在当前命名空间的父命名空间中查找,直到找到全局命名空间为止。

另一方面,如果你给一个变量赋值,那么这个变量只在本地命名空间中有效,除非你用global关键字特别声明。所以 a = 1 会在本地命名空间中创建一个新的名字变量a。这个新名字会优先于更高层命名空间中任何叫变量a的变量。

1

Python中的名字解析规则有时被称为LEGB规则,这个名字是根据不同的作用域命名的。

当你在一个函数内部使用一个没有前缀的名字时,Python会依次在四个地方查找这个名字:首先是局部(L)作用域,然后是任何外部(E)函数和lambda表达式的局部作用域,接着是全局(G)作用域,最后是内置(B)作用域。Python会在找到名字的第一个地方停止查找。如果在这个过程中没有找到这个名字,Python就会报错。

  • 名字的赋值默认会创建或改变局部名字。
  • 名字的引用最多会在四个作用域中查找:局部作用域,然后是外部函数(如果有的话),接着是全局作用域,最后是内置作用域。
  • 在global和nonlocal语句中声明的名字会分别映射到外部模块和函数的作用域。

换句话说,在一个函数def语句(或者一个lambda)内部赋值的所有名字默认都是局部的。函数可以自由使用在外部函数和全局作用域中赋值的名字,但如果想要改变这些名字,必须先声明它们为nonlocalsglobals

参考链接:http://goo.gl/woLW0F

8

示例

(下面有个总结)

这段话的意思是,如果一个变量在函数内部没有被赋值,那么它会被当作全局变量来处理。

这就解释了为什么下面的代码可以运行(a 被当作全局变量):

a = 1

def fn():
    print a  # This is "referencing a variable" == "reading its value"

# Prints: 1

但是,如果在函数内部给这个变量赋值,那么它会被当作局部变量来处理,这种情况在整个函数体内都是如此。

这包括在赋值之前的语句(见下面的例子)。

这就解释了为什么下面的代码能运行。在这里,a 被当作局部变量:

a = 1

def fn():
    print a 
    a = 2  # <<< We're adding this

fn()

# Throws: UnboundLocalError: local variable 'a' referenced before assignment

你可以通过在代码中使用 global a 这条语句来让 Python 把一个变量当作全局变量。如果这样做了,那么这个变量在整个函数体内都会被当作全局变量。

a = 1

def fn():
   global a  # <<< We're adding this
   print a
   a = 2

fn()
print a

# Prints: 1
# Then, prints: 2 (a changed in the global scope too)

总结

和你可能想的不同,Python不会在找不到局部变量 a 时自动回退到全局范围。

这意味着一个变量在整个函数体内要么是局部的,要么是全局的:它不能先是全局的然后再变成局部的。

至于一个变量是被当作局部还是全局,Python遵循以下规则:

  • 如果只引用而没有赋值,则是全局变量
  • 如果使用了 global 语句,则是全局变量
  • 如果至少赋值一次(并且没有使用 global),则是局部变量

进一步说明

实际上,“隐式全局”并不是真正的全局。可以这样理解:

  • “局部”意味着“在函数内部的某个地方”
  • “全局”意味着“在函数外部的某个地方”

所以,如果一个变量是“隐式全局”的(即“在函数外部”),那么会首先查找它的“外部作用域”:

a = 25

def enclosing():
   a = 2
   def enclosed():
       print a
   enclosed()

enclosing()

# Prints 2, as supplied in the enclosing scope, instead of 25 (found in the global scope)

现在,像往常一样,global 让你可以引用全局作用域。

a = 25

def enclosing():
   a = 2
   def enclosed():
       global a  # <<< We're adding this
       print a
   enclosed()

enclosing()

# Prints 25, as supplied in the global scope

如果你需要在 enclosed 中给 a 赋值,并且希望 a 的值在 enclosing 的作用域中改变,但不在全局作用域中改变,那么你需要使用 nonlocal,这是 Python 3 中的新特性。在 Python 2 中,你不能这样做。

撰写回答