Python如何处理全局变量?

3

我在Python中发现了一些非常奇怪的全局变量处理方式。我希望有人能够解释和证明这些惊喜!

A) 这段代码按预期输出10:

def func():
  print(a)
a = 10
func()

B) 这段代码会抛出一个关于引用过早的异常:

def func():
  print(a)
  a += 1
a = 10
func()

但是这段代码按预期输出 [10]:
def func():
  print(a)
  a.append(1)
a = [10]
func()

我可以理解的是,a 的类型会影响其范围,并且后面甚至还没有到达的语句将改变如何看待 a。 我知道我可以在函数开头使用 global a,但这太啰嗦了。

有人能告诉我 Python 处理其奇怪作用域所使用的规则吗?


1
这并不奇怪,Python鼓励良好的实践!如果您觉得需要声明“全局变量”,您应该重新考虑设计。当函数接受参数进行变异或者更好的情况下(通常),返回新版本而不是突变原始版本会更好。在您最后一个示例中,就像@Ignacio所说,您甚至没有将名称“a”绑定到其他值。您只是访问“a”的方法,因此这不应该令人惊讶。 - jamylak
不,我向您保证我的设计没问题。在单线程应用程序中,我有一个表示精度的全局变量。在函数之间传递精度值只是浪费时间。问题只会在工作精度略微增加时才会出现。 - LordLing
此外,我无法看出一条稍后从未执行的语句如何改变前面语句的含义。这怎么不奇怪呢?我不知道 Python 中是否还有其他类似情况。 - LordLing
1
你也可以认为,在文件结尾处发生语法错误会导致脚本在开始执行之前就崩溃,这很奇怪。它们都是防止错误的手段:让 a 改变父作用域和局部作用域中变量的含义非常令人困惑,也不太一致。 它要么是局部变量,要么不是。 从来没有两者都是。 - phant0m
2个回答

5
第二个实例重新绑定a,所以编译器为其生成了本地访问。而另外两个只是读取a,因此会执行正常的全局范围搜索。

通过在函数B中首先放置global a,您将获得您预期的行为。 - Odalrick
是否有一种方法,如果存在全局变量,则始终首先引用它们? - LordLing
不修改编译器是不可能的。 - Ignacio Vazquez-Abrams

4

基本上,有两条规则:

  1. 当你只读取一个变量(在一个作用域内),Python 会沿着作用域链遍历,直到找到该名称的变量。
  2. 当你至少写入一个变量时,在当前作用域中 Python 将始终创建该变量。

但是你可以改变第二条规则的行为:

  • 如果你想让一个名称引用模块级别的变量,可以使用 global my_module_variable。当你现在写入 my_module_variable 时,Python 不会创建一个本地变量。
  • 自 Python 3 以来,你还可以让名称引用嵌套作用域中的变量:使用 nonlocal my_non_local_variable 让它引用最近的封闭作用域中的变量。

你代码中的问题

B) 你正在使用 +=:你正在尝试写入变量。因此,规则 2 生效,它将在当前作用域中写入一个变量。然而,它还必须从中读取 (print(a)),但变量还没有值,因为你之前没有写入它。在一个函数中,Python 不允许你混合使用规则 1. 和规则2.

如果你想让 func() 在变量 a = 10 上运行,可以像这样更改代码:

>>>> def func()
        global a
        print(a)
        a += 1
>>>> a = 10
>>>> func()
10
>>>> func()
11

那么这意味着Python解释器在决定函数内部的变量是使用规则1还是规则2进行访问之前,需要解析整个函数体以确定应用哪个规则?在一个可能有数千行长的函数中?这意味着解释器的第一次扫描(生成字节码)会相当慢,不是吗? - blueFast
@gonvaled Python总是解析整个file.py文件并生成字节码。是的,你会为此付出一点启动时间。然而,第二次执行脚本时,字节码已经存在了。 - phant0m
好的,我有这样的印象,即解析器在执行代码的同时生成字节码(类似于shell所做的-当然没有字节码)。 - blueFast

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