变数 ( 全域、区域 )
一个变数的名称除了可以代表不同的东西,也表示“哪里可以使用”这个变数,这篇教学将会介绍全域变数和区域变数的用法和差异。
快速导览:
本篇使用的 Python 版本为 3.7.12,所有范例可使用 Google Colab 实作,不用安装任何软件 ( 参考:使用 Google Colab )
什么是全域变数和区域变数?
在 Python 里的主程序与每个函数,都有各自的名称空间 ( namespace ),简单的区分规则如下:
- 主程序定义“全域”的名称空间,在主程序定义的变数是“全域变数”。
- 个别函数定义“区域”的名称空间,个别函数里定义的变数就是“区域变数”。
- 每个名称空间里的变数名称都是“唯一的”。
- 不同名称空间内的变数名称可以相同,例如函数 A 可以定义 a 变数,函数 B 也可以定义 a 变数,两个 a 变数是完全不同的变数。
例如有个人叫做小华,在一个家庭里只会有一个小华,而不同的家庭可以有不同的小华 ( 不同的人,但是都叫做小华 ),不过如果这群小华去到学校,就必须要额外命名,才知道哪个小华是小华 ( 一间学校只会有一个小华,其他的可能是小华一号、小华二号...等 )
默认的名称空间
Python 默认提供三个名称空间,分别是内置默认 Built-in ( Python 默认的函数名称如 abs、char...等等 )、全域 Global 和区域 Local 三种,当使用变数时,会从最内层 ( 区域命名空间 ) 开始往外层搜索,直到找到对应的名称为止 ( 如果找不到就会抛出错误 )。
厘清到底用了哪个变数
下方的程序码,定义了两个变数 a,因为两个变数 a 处在不同的名称空间里,所以印出来的结果是不同的。
注意,函数名称也属于变数名称,如果将函数定义为 a,则会覆写全域变数 a 的内容。
a = 1 # 定義全域變數 a 等於 1
def hello(): # 定義 hello 函式
a = 2 # 定義區域變數 a 等於 2
print(a)
hello() # 2
print(a) # 1
如果在 hello 函数里没有定义变数 a,而是“单纯使用变数 a”,这时程序会先寻找 hello 函数的名称空间里是否有变数 a,如果找不到,就会往外层寻找,找到之后就会使用该变数的内容,以下方的例子,执行 hello 函数后就会印出 10。
a = 1
def hello():
print(a+9) # 使用全域變數的 a
hello() # 10
print(a) # 1
如果移除全域变数 a,执行过程最后就会发生错误,因为最后一个 print(a) 是寻找全域变数 a,因为找不到所以就会发生错误。
def hello():
a = 1
print(a)
hello()
print(a) # 發生錯誤,因為找不到變數 a
同样的道理,如果是不同函数,如果要互相呼叫对方的变数,也会发生错误。
def hello():
a = 1
print(a)
def test():
print(a)
hello()
test() # 發生錯誤,因為找不到變數 a
使用 global 修改全域变数
如果要在函数里修改全域变数,可以使用“global 全域變數”的方式。
a = 1 # 定義全域變數 a 等於 1
def hello(): # 定義 hello 函式
global a # 聲明下方的 a 為全域變數 a
a = 2 # 修改 a 為 2
print(a) # 1
hello() # 執行 hello 函式
print(a) # 2 ( 全域變數 a 被修改為 2 )
全域变数和区域变数容易遇到的陷阱
如果变数的内容是串列、字典或集合,在处理“全域变数和区域变数”时,与处理“多个变数同时赋值”时一样,很容易会遇到赋值的陷阱,因为变数只是“标签”,当多个变数同时指向一个串列、字典或集合时,只要变数内容被修改 ( 并非使用等号赋值 ),不论这个变数是全域还是区域变数,另外一个变数内容也会跟着更动 ( 延伸阅读:设定多个变数的陷阱 )。
下方的例子执行后,f1 函数的 a 不受作用域的影响,使用 append 发生“改变”后,不论 a 在何处都会被影响,连带 b 也被影响,但 c 因为是使用等号“宣告赋值”,就会转变成“区域变数”,因此在 f1 函数作用域之外的 c 就不会被影响,d 也不会被影响,不过如果在 f1 的开头加上 global c,等同于将 c 从区域变数提升到全域变数,f2 里的 c 就会被影响。
a = []
b = a
c = []
d = c
def f1():
# global c # 如果加上這行,f2 裡的 c 就會被影響
a.append(1)
c = [1]
print(a) # [1]
print(b) # [1] # 被影響
print(c) # [1]
print(d) # [] # 不受影響
def f2():
print(a) # [1] # 被影響
print(b) # [1] # 被影響
print(c) # [] # 不受影響,但如果 f1 加上 global c,此處就會被影響
print(d) # [] # 不受影響
f1()
f2()
global() 和 local()
global() 和 local() 是两个可以印出目前变数的方法:
- global():回传一个字典,内容是“全域名称空间”的内容。
- local():回传一个字典,内容是“区域名称空间”的内容。
下方的程序码,执行 hello 函数后会印出区域名称空间和全域名称空间的内容。
a = 1
def hello():
a = 1
print(locals())
print(globals())
hello()
print(a)
如果将 locals() 放到全域名称空间里,则印出来的结果和 global() 相同。
a = 1
def hello():
a = 1
hello()
print(locals())
print(globals())
微信扫码关注
抖音扫码关注