1. 程式人生 > >Python基本語法_變數作用域LEGB

Python基本語法_變數作用域LEGB

目錄

軟體系統

  • 系統
    • 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

在作用域中定義的變數,一般只在作用域中有效。
注意:在if-elif-else、for-else、while、try-except\try-finally等關鍵字的語句塊中並不會產成作用域。

In [15]: if True:
   ....:     variable = 100
   ....:     print variable
   ....: print "******"
   ....: print variable
   ....: 
100     
******
100      #變數variableif語句塊內或外都表示同一個變數

作用域的型別

在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

例項說明

對變數的引用

在不同作用域中可以存在相同的變數名,當出現這種情況的時候,對LEGB法則的理解就顯得非常重要了,否則就會有隻能知其然而不知其所以然的感覺。
例項1

def func():
    variable = 300
    print variable

variable = 100
func()
print variable

In [11]: %run testScopt.py
300     #優先搜尋函式func()內的區域性變數
100

例項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

例子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

上面的例子會報出錯誤,因為在執行程式時的預編譯能夠在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

比較例子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

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

注意: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

NOTE:Python 2 中沒有 nonlocal 關鍵字,但可以使用轉換變數名的方式來防止 UnboundLocalError: local variable 'x' referenced before assignment, 的錯誤。

def outter():
    x = 1
    def inner():
        print("inner is called, x=", x)
        x = 2
    return inner
outter()()

會觸發 UnboundLocalError: local variable 'x' referenced before assignment,,因為 Python 解析器會認為在 local 作用域中 x 沒有完成賦值就被呼叫了。

  • Python 3 使用 nonlocal 關鍵字:
#!/usr/bin/python
def outter():
    x = 1
    def inner():
        nonlocal x
        print("inner is called, x=", x)
        x = 2
    return inner

outter()()
  • Python 2可以轉換變數名:
#!/usr/bin/python
def outter():
    x = 1
    def inner():
        y = x
        print("inner is called, x=", y)
        y = 2
    return inner

outter()()

名稱空間和作用域的區別

  • 名稱空間:是一個包含了該空間中所有變數和變數值的字典,不同的名稱空間之間是相互隔離的,所以在不同的名稱空間可以建立同名變數,通過句點識別符號來呼叫和區別,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}
  • locals()
>>> locals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None, 'x': 3, '__package__': None}

NOTE: 因為這裡的兩個例子都是在全域性環境中呼叫,globals()和locals()函式的返回值是一致的。這兩個函式有下面點需要注意的地方:
1. 根據呼叫地方的不同,globals()和locals()函式可被用來返回全域性和區域性名稱空間裡的名字。
2. 如果在函式內部呼叫locals(),返回的是所有能在該函式裡訪問的命名。
3. 如果在函式內部呼叫globals(),返回的是所有在該函式裡能訪問的全域性名字。
4. 兩個函式的返回型別都是字典。所以能用keys()函式摘取變數名。

  • 作用域:是一個變數能夠有效的區域,全域性作用域的全域性變數在整個模組中有效,區域性作用域中的區域性變數只在類或函式中有效。建立一個作用域會同時生成一個名稱空間,並且作用域包圍了其名稱空間。作用域是為了實現變數查詢的路徑,就如上文所述,如何區域性作用域中含有於全域性作用域同名的變數時,區域性作用域會遮蔽掉全域性作用域,這是因為變數的查詢路徑中,區域性作用域要先於全域性作用域,然後再到相對的名稱空間中獲取變數的值。