在被调用函数内赋值的变量,处于该函数的“局部作用域”。 在所有函数之外赋值的变量,属于“全局作用域”。 处于局部作用域的变量,被称为“局部变量”。 处于全局作用域的变量,被称为“全局变量”。 一个变量必是其中一种,不能既是局部的又是全局的。
不止一次地看到人们在抱怨,全局变量是罪恶之源,根本就不应该使用它们。 但是不认为这个结论是显而易见正确的。希望能听到一次有关全局变量的严肃认真的讨论。 论坛中的朋友们很多都提到了他们都正在编程中使用全局变量。
先从全局变量的优点谈起。一般来说,全局变量是公认的在各个VI之间传递数据的有效方法, 比起其它方式的全局变量(个人意见)更容易管理,因为假如使用了一个簇作为全局数据, 没有办法确定在何处使用了它们,可能需要自己创建一个文件记录它们使用的位置。 但是全局变量则不然,通过全局变量的右键快捷菜单,可以很容找到引用全局变量的位置。
使用全局变量有两个不利之处,其一,引用全局变量需要创建数据的拷贝, 这可能会导致潜在的竞争条件或者导致数据的丢失。其二,使用全局变量会中断数据流程。 可以将“作用域”看成是变量的容器。当作用域被销毁时, 所有保存在该作用域内的变量的值就被丢弃了。只有一个全局作用域, 它是在程序开始时创建的。如果程序终止,全局作用域就被销毁, 它的所有变量就被丢弃了。否则,下次运行程序的时候, 这些变量就会记住它们上次运行时的值。
一个函数被调用时,就创建了一个局部作用域。 在这个函数内赋值的所有变量,存在于该局部作用域内。 该函数返回时,这个局部作用域就被销毁了, 这些变量就丢失了。下次调用这个函数, 局部变量不会记得该函数上次被调用时它们保存的值。 作用域很重要,理由如下:
- 全局作用域中的代码不能使用任何局部变量;
- 但是,局部作用域可以访问全局变量;
- 一个函数的局部作用域中的代码,不能使用其他局部作用域中的变量。
- 如果在不同的作用域中,可以用相同的名字命名不同的变量。
也就是说,可以有一个名为
spam
的局部变量,和一名为spam
的全局变量。
Python 有不同的作用域,而不是让所有东西都成全局变量,这是有理由的。 这样一来,当特定函数调用中的代码修改变量时, 该函数与程序其他部分的交互,只能通过它的参数和返回值。 这缩小了可能导致缺陷的代码作用域。如果程序只包含全局变量, 又有一个变量赋值错误的缺陷,那就很难追踪这个赋值错误发生的位置。 它可能在程序的任何地方赋值,而程序可能有几百到几千行! 但如果缺陷是因为局部变量错误赋值,就会知道, 只有那一个函数中的代码可能产生赋值错误。
虽然在小程序中使用全局变量没有太大问题,但当程序变得越来越大时,依赖全局变量就是一个坏习惯。
变量到底是什么呢?可将其视为指向值的名称。因此,执行赋值语句 x = 1
后,名称 x
指向值 1
。
这几乎与使用字典时一样(字典中的键指向值),只是使用的是“看不见”的字典。
实际上, 这种解释已经离真相不远。有一个名为 vars()
的内置函数,它返回这个不可见的字典:
x = 1
scope = vars()
scope['x']
1
这个值可以进行赋值:
scope['x'] += 1
x
2
警告: 一般而言,不应修改 vars
返回的字典,因为根据Python官方文档的说法,这样做的结果是不确定的。
换而言之,可能得不到想要的结果。
这种“看不见的字典”称为命名空间或作用域。那么有多少个命名空间呢?除全局作用域外, 每个函数调用都将创建一个。
def foo():
x = 42
x = 1
foo()
x
1
在这里,函数 foo
修改(重新关联)了变量 x
,但当最终查看时,它根本没变。
这是因为调用 foo
时创建了一个新的命名空间,供 foo
中的代码块使用。
赋值语句 x = 42
是在这个内部作用域(局部命名空间)中执行的,不影响外部(全局)作用域内的 x
。
在函数内使用的变量称为局部变量(与之相对的是全局变量)。
参数类似于局部变量,因此参数与全局变量同名不会有任何问题。
def output(x):
print(x)
x = 1
y = 2
output(y)
2
到目前为止一切顺利。但如果要在函数中访问全局变量呢?如果只是想读取这种变量的值 (不重新关联它),通常不会有任何问题。
def combine(parameter):
print(parameter + external)
external = 'berry'
combine('Shrub')
Shrubberry
警告:像这样访问全局变量是众多bug的根源。务必慎用全局变量。
读取全局变量的值通常不会有问题,但还是存在出现问题的可能性。 如果有一个局部变量或参数与要访问的全局变量同名,就无法直接访问全局变量, 因为它被局部变量遮 住了。
如果需要,可使用函数 globals
来访问全局变量。
这个函数类似于 vars()
,返回一个包含全 局变量的字典( locals
返回一个包含局部变量的字典)。
例如,在前面的示例中,如果有一个名为parameter
的全局变量,就无法在函数 combine
中访问它,因为有一个与之同名的参数。
然而,必要时可使用 globals()['parameter']
来访问它。
def combine(parameter):
print(parameter + globals()['parameter'])
parameter = 'berry'
combine('Shrub')
Shrubberry
重新关联全局变量(使其指向新值)是另一码事。 在函数内部给变量赋值时,该变量默认为 局部变量,除非明确地告诉Python它是全局变量。 那么如何将这一点告知Python呢?
x = 1
def change_global():
global x
x = x + 1
change_global()
x
2
Python函数可以嵌套,即可将一个函数放在另一个函数内,如下所示:
def foo():
def bar():
print("Hello, world!")
bar()
嵌套通常用处不大,但有一个很突出的用途:使用一个函数来创建另一个函数。 这意味着可像下面这样编写函数:
def multiplier(factor):
def multiplyByFactor(number):
return number * factor
return multiplyByFactor
在这里,一个函数位于另一个函数中,且外面的函数返回里面的函数。 也就是返回一个函数,而不是调用它。重要的是,返回的函数能够访问其定义所在的作用域。 换而言之,它携带着自己所在的环境(和相关的局部变量)!
每当外部函数被调用时,都将重新定义内部的函数,而变量 factor
的值也可能不同。
由于 Python 的嵌套作用域,可在内部函数中访问这个来自外部局部作用域(multiplier)的变量,如下所示:
double = multiplier(2)
double(5)
10
triple = multiplier(3)
triple(3)
9
multiplier(5)(4)
20
像 multiplyByFactor
这样存储其所在作用域的函数称为闭包。
通常,不能给外部作用域内的变量赋值,但如果一定要这样做,可使用关键字 nonlocal
。
这个关键字的用法与 global
很像,能够给外部作用域(非全局作用域)内的变量赋值。
def spam():
eggs = 31337
spam()
# 以下变量定义在 spam函数中,直接打印会出错。
# print(eggs) #此代码会报错
def spam():
eggs = 99
bacon()
print(eggs)
def bacon():
ham = 101
eggs =0
spam()
99
在程序开始运行时, spam()
函数被调用,
创建了一个局部作用域。局部变量 eggs
被赋值为 99
。
bacon()
函数被调用,创建了第二个局部作用域。
多个局部作用域能同时存在。在这个新的局部作用域中,
局部变量 ham
被赋值为 101
。局部变量 eggs
(与 spam()
的局部作用域中的那个变量不同)也被创建,并赋值为0。
当 bacon()
返回时,这次调用的局部作用域被销毁。
程序执行在 spam()
函数中继续,打印出 eggs
的值。
因为 spam()
调用的局部作用域仍然存在,
eggs
变量被赋值为 99
。这就是程序的打印输出。
要点在于,一个函数中的局部变量完全与其他函数中的局部变量分隔开来。
全局变量可以在局部作用域中读取
请看以下程序:
def spam():
print(eggs)
eggs = 42
spam()
print(eggs)
42 42
def spam():
eggs = 'spam local'
print(eggs) # prints 'pam local'
def bacon():
eggs = 'bacon local'
print(eggs) # prints 'bacon local'
spam()
print(eggs) # prints 'bacon local'
eggs = 'global'
bacon()
print(eggs) # prints 'global'
bacon local spam local bacon local global
在这个程序中,实际上有3个不同的变量,
但令人迷惑的是,它们都名为 eggs
。
这些变量是:
eggs = 'spam local'
中,名为eggs
的变量,存在于spam()
被调用时的局部作用域;eggs = 'bacon local'
中,名为eggs
的变量,存在于bacon()
被调用时的局部作用域;eggs = 'global'
中,名为eggs
的变量,存在于全局作用域。
因为这3个独立的变量都有相同的名字,追踪某一个时刻使用的是哪个变量, 可能比较麻烦。这就是应该避免在不同作用域内使用相同变量名的原因。