07.python函式作用域global、nonlocal、LEGB
函式作用域
作用域
一個識別符號的課件範圍,這就是識別符號的作用域,一般常說的是變數的作用域
def foo():
x = 100
print(x) # 可以訪問到嗎
上例中x不可以訪問到,會丟擲異常(NameError: name 'x' is not defined),原因在於函式是一個封裝,它會開闢一個作用域,x變數被限制在這個作用域中,所以在函式外部x變數不可見。
注意:每一個函式都會開闢一個作用域
作用域分類
-
全域性作用域
-
在整個程式執行環境中都可見
-
全域性作用域中的變數稱為全域性變數global
-
-
區域性作用域
-
在函式、類等內部可見
-
區域性作用域中的變數稱為區域性變數
-
也稱為本地作用域local
-
# 區域性變數
def fn1():
x = 1 # 區域性作用域,x為區域性變數,使用範圍在fn1內
def fn2():
print(x) # x能列印嗎?可見嗎?為什麼?
print(x) # x能列印嗎?可見嗎?為什麼?
# 全域性變數
x = 5 # 全域性變數,也在函式外定義
def foo():
print(x) # 可見嗎?為什麼?
foo()
-
一般來講外部作用域變數可以在函式內部可見,可以使用
-
反過來,函式內部的區域性變數,不能在函式外部看到
函式巢狀
在一個函式中定義了另外一個函式
def outer():
def inner():
print("inner")
inner()
print("outer")
outer() # 可以嗎?
inner() # 可以嗎?
內部函式inner不能在外部直接使用,會拋NameError異常,因為它在函式外部不可見。
其實,inner不過就是一個識別符號,就是一個函式outer內部定義的變數而已。
巢狀結構的作用域
對比下面巢狀結構,程式碼執行的效果
def outer1(): o = 65 def inner(): print('inner', o, chr(o)) inner() print('outer', o, chr(o)) outer1() # 執行後,列印什麼 def outer2(): o = 65 def inner(): o = 97 print('inner', o, chr(o)) inner() print('outer', o, chr(o)) outer2() # 執行後,列印什麼
從執行的結果來看:
-
外層變數在內部作用域可見
-
內層作用域inner中,如果定義了 o = 97 ,相當於在當前函式inner作用域中重新定義了一個新的
-
變數o,但是,這個o並不能覆蓋掉外部作用域outer2中的變數。只不過對於inner函式來說,其只能可見自己作用域中定義的變數o了
內建函式 | 函式簽名 | 說明 |
---|---|---|
chr | chr(i) | 通過unicode編碼返回對應字元 |
ord | ord(c) | 獲得字元對應的unicode |
print(ord('中'), hex(ord('中')), '中'.encode(),'中'.encode('gbk'))
chr(20013) # '中'
chr(97)
一個賦值語句的問題
再看下面左右2個函式
左邊函式 | 右邊函式 |
---|---|
正常執行,函式外部的變數在函式內部可見 | 執行錯誤嗎,為什麼?難道函式內部又不可見了?y = x + 1可以正確執行,可是為什麼x += 1卻不能正確執行? |
仔細觀察函式2返回的錯誤指向x += 1,原因是什麼呢?
x = 5
def foo():
x += 1
foo() # 報錯如下
原因分析:
-
x += 1 其實是 x = x + 1
-
只要有"x="出現,這就是賦值語句。相當於在foo內部定義一個區域性變數x,那麼foo內部所有x都是這個區域性變數x了
-
x = x + 1 相當於使用了局部變數x,但是這個x還沒有完成賦值,就被右邊拿來做加1操作了
x = 5
def foo(): # 函式被直譯器解釋,foo指向函式物件,同時直譯器會理解x是什麼作用域
print(x) # x 在函式解析時就被直譯器判定為區域性變數
x += 1 # x = x + 1
foo() # 呼叫時
如何解決這個常見問題?
global語句
x = 5
def foo():
global x # 全域性變數
x += 1
print(x)
foo()
-
使用global關鍵字的變數,將foo內的x宣告為使用外部的全域性作用域中定義的x
-
全域性作用域中必須有x的定義
如果全域性作用域中沒有x定義會怎樣?
注意,下面試驗如果在ipython、jupyter中做,上下文執行環境中有可能有x的定義,稍微不注意,就測試不出效果
# 有錯嗎?
def foo():
global x
x += 1
print(x)
foo()
# 有錯嗎?
def foo():
global x
x = 10
x += 1
print(x)
foo()
print(x) # 可以嗎
使用global關鍵字定義的變數,雖然在foo函式中宣告,但是這將告訴當前foo函式作用域,這個x變數將使用外部全域性作用域中的x。
即使是在foo中又寫了 x = 10 ,也不會在foo這個區域性作用域中定義區域性變數x了。
使用了global,foo中的x不再是區域性變量了,它是全域性變數。
總結
-
x+=1 這種是特殊形式產生的錯誤的原因?先引用後賦值,而python動態語言是賦值才算定義,才能被引用。解決辦法,在這條語句前增加x=0之類的賦值語句,或者使用global 告訴內部作用域,去全域性作用域查詢變數定義
-
內部作用域使用 x = 10 之類的賦值語句會重新定義區域性作用域使用的變數x,但是,一旦這個作用域中使用 global 宣告x為全域性的,那麼x=5相當於在為全域性作用域的變數x賦值
global使用原則
-
外部作用域變數會在內部作用域可見,但也不要在這個內部的區域性作用域中直接使用,因為函式的目的就是為了封裝,儘量與外界隔離
-
如果函式需要使用外部全域性變數,請儘量使用函式的形參定義,並在呼叫傳實參解決
-
一句話:不用global。學習它就是為了深入理解變數作用域
閉包
自由變數:未在本地作用域中定義的變數。例如定義在內層函式外的外層函式的作用域中的變數
閉包:就是一個概念,出現在巢狀函式中,指的是內層函式引用到了外層函式的自由變數,就形成了閉包。很多語言都有這個概念,最熟悉就是JavaScript
def counter():
c = [0]
def inc():
c[0] += 1 # 報錯嗎? 為什麼 # line 4
return c[0]
return inc
foo = counter() # line 8
print(foo(), foo()) # line 9
c = 100
print(foo()) # line 11
上面程式碼有幾個問題:
-
第4行會報錯嗎?為什麼
-
第9行列印什麼結果?
-
第11行列印什麼結果?
程式碼分析
-
第8行會執行counter函式並返回inc對應的函式物件,注意這個函式物件並不釋放,因為有foo記著
-
第4行會報錯嗎?為什麼
- 不會報錯,c已經在counter函式中定義過了。而且inc中的使用方式是為c的元素修改值,而不是重新定義c變數
-
第9行列印什麼結果?
- 列印 1 2
-
第11行列印什麼結果?
-
列印 3
-
第9行的c和counter中的c不一樣,而inc引用的是自由變數正是counter中的變數c
-
這是Python2中實現閉包的方式,Python3還可以使用nonlocal關鍵字
再看下面這段程式碼,會報錯嗎?使用global能解決嗎?
def counter():
count = 0
def inc():
count += 1
return count
return inc
foo = counter()
print(foo(), foo())
上例一定出錯,使用gobal可以解決
def counter():
global count
count = 0
def inc():
global count
count += 1
return count
return inc
foo = counter()
print(foo(), foo())
count = 100
print(foo()) # 列印幾?
上例使用global解決,這是全域性變數的實現,而不是閉包了。
如果要對這個普通變數使用閉包,Python3中可以使用nonlocal關鍵字。
nonlocal語句
nonlocal:將變數標記為不在本地作用域定義,而是在上級的某一級區域性作用域中定義,但不能是全域性作用域中定義。
def counter():
count = 0
def inc():
nonlocal count # 宣告變數count不是本地變數
count += 1
return count
return inc
foo = counter()
print(foo(), foo())
count 是外層函式的區域性變數,被內部函式引用。
內部函式使用nonlocal關鍵字宣告count變數在上級作用域而非本地作用域中定義。
程式碼中內層函式引用外部區域性作用域中的自由變數,形成閉包。
上例是錯誤的,nonlocal 宣告變數 a 不在當前作用域,但是往外就是全域性作用域了,所以錯誤。
函式的銷燬
定義一個函式就是生成一個函式物件,函式名指向的就是函式物件。
可以使用del語句刪除函式,使其引用計數減1。
可以使用同名識別符號覆蓋原有定義,本質上也是使其引用計數減1。
Python程式結束時,所有物件銷燬。
函式也是物件,也不例外,是否銷燬,還是看引用計數是否減為0。
變數名解析原則LEGB
-
Local,本地作用域、區域性作用域的local名稱空間。函式呼叫時建立,呼叫結束消亡
-
Enclosing,Python2.2時引入了巢狀函式,實現了閉包,這個就是巢狀函式的外部函式的名稱空間
-
Global,全域性作用域,即一個模組的名稱空間。模組被import時建立,直譯器退出時消亡
-
Build-in,內建模組的名稱空間,生命週期從python直譯器啟動時建立到直譯器退出時消亡。例如print(open),print和open都是內建的變數
所以一個名詞的查詢順序就是LEGB
作者:宋城西柵 出處:http://www.cnblogs.com/shwee/-------------------------------------------
個性簽名:獨學而無友,則孤陋而寡聞。做一個靈魂有趣的人!
如果覺得這篇文章對你有小小的幫助的話,記得在右下角點個“推薦”哦,博主在此感謝!
萬水千山總是情,打賞一分行不行,所以如果你心情還比較高興,也是可以掃碼打賞博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!