目錄
軟件系統
- 系統
- Ubuntu 14.04
- 軟件
- Python 2.7.3
- IPython 4.0.0
變量的作用域
在Python程序中創建、改變、查找變量名時,都是在一個保存變量名的空間中進行,我們稱之爲命名空間,也被稱之爲作用域。Python的作用域是靜態的,在源代碼中變量名被賦值的位置決定了該變量能被訪問的範圍。即Python變量的作用域由變量所在源代碼中的位置決定。
高級語言對數據類型的使用過程
一般的高級語言在使用變量時,都會有下面4個過程。當然在不同的語言中也會有着區別。
1. 聲明變量:讓編輯器知道有這一個變量的存在
2. 定義變量:爲不同數據類型的變量分配內存空間
3. 初始化:賦值,填充分配好的內存空間
4. 引用:通過引用對象(變量名)來調用內存對象(內存數據)
作用域的產生
就作用域而言,Python與C有着很大的區別,在Python中並不是所有的語句塊中都會產生作用域。只有當變量在Module(模塊)、Class(類)、def(函數)中定義的時候,纔會有作用域的概念。
In [19]: %pycat testScopt.py
#!/usr/bin/env python
def func():
variable = 100
print variable
print variable
NameError: name 'variable' is not defined
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
在作用域中定義的變量,一般只在作用域中有效。
注意:在if-elif-else、for-else、while、try-except\try-finally等關鍵字的語句塊中並不會產成作用域。
In [15]: if True:
....: variable = 100
....: print variable
....: print "******"
....: print variable
....:
100
******
100 #變量variable在if語句塊內或外都表示同一個變量
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
作用域的類型
在Python中,使用一個變量時並不嚴格要求需要預先聲明它,但是在真正使用它之前,它必須被綁定到某個內存對象(被定義、賦值);這種變量名的綁定將在當前作用域中引入新的變量,同時屏蔽外層作用域中的同名變量。
L(local)局部作用域
局部變量:包含在def關鍵字定義的語句塊中,即在函數中定義的變量。每當函數被調用時都會創建一個新的局部作用域。Python中也有遞歸,即自己調用自己,每次調用都會創建一個新的局部命名空間。在函數內部的變量聲明,除非特別的聲明爲全局變量,否則均默認爲局部變量。有些情況需要在函數內部定義全局變量,這時可以使用global關鍵字來聲明變量的作用域爲全局。局部變量域就像一個 棧,僅僅是暫時的存在,依賴創建該局部作用域的函數是否處於活動的狀態。所以,一般建議儘量少定義全局變量,因爲全局變量在模塊文件運行的過程中會一直存在,佔用內存空間。
注意:如果需要在函數內部對全局變量賦值,需要在函數內部通過global語句聲明該變量爲全局變量。
E(enclosing)嵌套作用域
E也包含在def關鍵字中,E和L是相對的,E相對於更上層的函數而言也是L。與L的區別在於,對一個函數而言,L是定義在此函數內部的局部作用域,而E是定義在此函數的上一層父級函數的局部作用域。主要是爲了實現Python的閉包,而增加的實現。
G(global)全局作用域
即在模塊層次中定義的變量,每一個模塊都是一個全局作用域。也就是說,在模塊文件頂層聲明的變量具有全局作用域,從外部開來,模塊的全局變量就是一個模塊對象的屬性。
注意:全局作用域的作用範圍僅限於單個模塊文件內
B(built-in)內置作用域
系統內固定模塊裏定義的變量,如預定義在__builtin__ 模塊內的變量。
變量名解析LEGB法則
搜索變量名的優先級:局部作用域 > 嵌套作用域 > 全局作用域 > 內置作用域
LEGB法則:
當在函數中使用未確定的變量名時,Python會按照優先級依次搜索4個作用域,以此來確定該變量名的意義。首先搜索局部作用域(L),之後是上一層嵌套結構中def或lambda函數的嵌套作用域(E),之後是全局作用域(G),最後是內置作用域(B)。按這個查找原則,在第一處找到的地方停止。如果沒有找到,則會出發NameError錯誤。
一個LEGB的例子:
#!/usr/bin/env python
#conding:utf8
globalVar = 100 #G
def test_scope():
enclosingVar = 200 #E
def func():
localVar = 300 #L
print __name__ #B
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
實例說明
對變量的引用
在不同作用域中可以存在相同的變量名,當出現這種情況的時候,對LEGB法則的理解就顯得非常重要了,否則就會有隻能知其然而不知其所以然的感覺。
實例1:
def func():
variable = 300
print variable
variable = 100
func()
print variable
In [11]: %run testScopt.py
300 #優先搜索函數func()內的局部變量
100
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
實例2:
In [29]: %pycat testScopt.py
#!/usr/bin/env python
#conding:utf8
def test_scopt():
variable = 200
print variable
def func():
print variable #這裏的變量variable在E中綁定了內存對象200,爲函數func()引入了一個新的變量
func()
variable = 100
test_scopt()
print variable
In [30]: %run testScopt.py
200
200 #在函數func()中無法找到變量variable,升級到test_scopt()中尋找
100
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
例子3:
In [9]: %pycat testScopt.py
#!/usr/bin/env python
#conding:utf8
variable = 300
def test_scopt():
print variable #variable是test_scopt()的局部變量,但是在打印時並沒有綁定內存對象。
variable = 200
test_scopt()
print variable
UnboundLocalError: local variable 'variable' referenced before assignment
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
上面的例子會報出錯誤,因爲在執行程序時的預編譯能夠在test_scopt()中找到局部變量variable(對variable進行了賦值)。在局部作用域找到了變量名,所以不會升級到嵌套作用域去尋找。但是在使用print語句將變量variable打印時,局部變量variable並有沒綁定到一個內存對象(沒有定義和初始化,即沒有賦值)。本質上還是Python調用變量時遵循的LEGB法則和Python解析器的編譯原理,決定了這個錯誤的發生。所以,在調用一個變量之前,需要爲該變量賦值(綁定一個內存對象)。
注意:爲什麼在這個例子中觸發的錯誤是UnboundLocalError而不是NameError:name ‘variable’ is not defined。因爲變量variable不在全局作用域。Python中的模塊代碼在執行之前,並不會經過預編譯,但是模塊內的函數體代碼在運行前會經過預編譯,因此不管變量名的綁定發生在作用域的那個位置,都能被編譯器知道。Python雖然是一個靜態作用域語言,但變量名查找是動態發生的,直到在程序運行時,纔會發現作用域方面的問題。
例子4:
In [2]: %pycat testScopt.py
#!/usr/bin/env python
#conding:utf8
variable = 300
def test_scopt():
print variable #沒有在局部作用域找到變量名,會升級到嵌套作用域尋找,並引入一個新的變量到局部作用域(將局部變量variable賦值爲300)。
# variable = 200
test_scopt()
print variable
In [3]: %run testScopt.py
300
300
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
比較例子3和4能夠更好的理解LEGB的過程。
對變量的修改
一個non-L的變量相對於L而言,默認是隻讀而不能修改的。如果希望在L中修改定義在non-L的變量,爲其綁定一個新的值,Python會認爲是在當前的L中引入一個新的變量(即便內外兩個變量重名,但卻有着不同的意義)。即在當前的L中,如果直接使用non-L中的變量,那麼這個變量是隻讀的,不能被修改,否則會在L中引入一個同名的新變量。這是對上述幾個例子的另一種方式的理解。
注意:而且在L中對新變量的修改不會影響到non-L的。當你希望在L中修改non-L中的變量時,可以使用global、nonlocal關鍵字。
In [18]: %pycat testScopt.py
#!/usr/bin/env python
#conding:utf8
variable = 100
def test_scopt():
variable = 200 #在L中引入一個新變量,覆蓋non-L中的變量
print variable
test_scopt()
print variable
In [19]: %run testScopt.py
200
100
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
global關鍵字
希望在L中修改G中的變量。
In [57]: %pycat test.py
spam = 99
def tester():
def nested():
global spam
print('current=',spam)
spam = 200
return nested
tester()()
print spam
In [58]: %run test.py
('current=', 99)
200
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
注意:tester()()表示會自動調用函數tester()的返回值,且此返回值必須爲可調用類型,即存在__call__方法。返回一個函數,所以也會執行返回的函數體代碼。
nonlocal關鍵字
在L中修改E中的變量。這是Python3.x增加的新特性,在python2.x中還是無法使用。
def outer():
count = 10
def inner():
nonlocal count
count = 20
print(count)
inner()
print(count)
outer()
#20
#20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
命名空間和作用域的區別
- 命名空間:是一個包含了該空間中所有變量和變量值的字典,不同的命名空間之間是相互隔離的,所以在不同的命名空間可以創建同名變量,通過句點標識符來調用和區別,EG.
JmilkFan.name
&fanguiju.name
JmilkFan 和 fanguiju 是兩個不同的命名空間。Python 內置了兩個查詢命名空間的字典的內置函數:globals()
>>> x = 3
>>> globals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None, 'x': 3, '__package__': None}
- 1
- 2
- 3
locals()
>>> locals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None, 'x': 3, '__package__': None}
- 1
- 2
NOTE: 因爲這裏的兩個例子都是在全局環境中調用,globals()和locals()函數的返回值是一致的。這兩個函數有下面點需要注意的地方:
1. 根據調用地方的不同,globals()和locals()函數可被用來返回全局和局部命名空間裏的名字。
2. 如果在函數內部調用locals(),返回的是所有能在該函數裏訪問的命名。
3. 如果在函數內部調用globals(),返回的是所有在該函數裏能訪問的全局名字。
4. 兩個函數的返回類型都是字典。所以能用keys()函數摘取變量名。
- 作用域:是一個變量能夠有效的區域,全局作用域的全局變量在整個模塊中有效,局部作用域中的局部變量只在類或函數中有效。創建一個作用域會同時生成一個命名空間,並且作用域包圍了其命名空間。作用域是爲了實現變量查詢的路徑,就如上文所述,如何局部作用域中含有於全局作用域同名的變量時,局部作用域會屏蔽掉全局作用域,這是因爲變量的查詢路徑中,局部作用域要先於全局作用域,然後再到相對的命名空間中獲取變量的值。