Python基本語法_變量作用域

目錄

軟件系統

  • 系統 
    • 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      #變量variableif語句塊內或外都表示同一個變量
  • 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()函數摘取變量名。

  • 作用域:是一個變量能夠有效的區域,全局作用域的全局變量在整個模塊中有效,局部作用域中的局部變量只在類或函數中有效。創建一個作用域會同時生成一個命名空間,並且作用域包圍了其命名空間。作用域是爲了實現變量查詢的路徑,就如上文所述,如何局部作用域中含有於全局作用域同名的變量時,局部作用域會屏蔽掉全局作用域,這是因爲變量的查詢路徑中,局部作用域要先於全局作用域,然後再到相對的命名空間中獲取變量的值。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章