Python學習之==>裝飾器
在Python中,裝飾器和迭代器、生成器都是非常重要的高階函式。
在講裝飾器之前,我們先要學習以下三個內容:
一、函式的作用域
1、作用域介紹
Python中的作用域分為四種情況:
- L:local,區域性作用域,即函式中定義的變數;
- E:enclosing,巢狀的父級函式的區域性作用域,即包含此函式的上級函式的區域性作用域,但不是全域性的;
- G:globa,全域性變數,就是模組級別定義的變數;
- B:built-in,系統固定模組裡面的變數,比如int, bytearray等。
搜尋變數的優先順序順序依次是:L –> E –> G –>B,即:區域性作用域>外層作用域>當前模組中的全域性>Python內建作用域。
1 x = int(2.9) # int built-in 2 3 g_count = 0 # global 4 5 def outer(): 6 o_count = 1 # enclosing 7 def inner(): 8 i_count = 2 # local 9 print(o_count) 10 print(i_count) # 找不到 11 inner() 12 outer() 13 14 print(o_count) #找不到
當然,local和enclosing是相對的,enclosing變數相對上層來說也是local。
2、作用域的產生
在Python中,只有函式(def、lambda)、類(class)以及模組(module)才會引入新的作用域,其它的程式碼塊(如if、try、for等)是不會引入新的作用域的,如下程式碼:
1 if 2>1: 2 x = 1 3 print(x) # 1
if並沒有引入一個新的作用域,x仍處在當前作用域中,後面程式碼可以使用。
1 def test(): 2 x = 2 3 print(x) # NameError: name 'x2' is not defined
上面這段程式碼則會報錯,因為def、class、lambda是可以引入新作用域的。
3、變數的修改
1 x = 6 2 def f(): 3 print(x) 4 x = 5 5 f() 6 7 # 錯誤的原因在於print(x)時,直譯器會在區域性作用域找,會找到x=5(函式已經載入到記憶體),但x使用在宣告前了,所以報錯: 8 # local variable 'x' referenced before assignment.如何證明找到了x=5呢?簡單:註釋掉x=5,x=6,報錯為:name 'x' is not defined 9 10 # 同理 11 x = 6 12 def f(): 13 x += 1 # local variable 'x' referenced before assignment. 14 f()
4、global關鍵字
當內部作用域想修改外部作用域的變數時,就要用到global和nonlocal關鍵字了,當修改的變數是在全域性作用域(global作用域)上的,就要使用global先宣告一下,程式碼如下:
1 count = 10 2 def outer(): 3 global count 4 print(count) #10 5 count = 100 6 print(count) #100 7 outer() 8 print(count) #100
5、nonlocal關鍵字
global關鍵字宣告的變數必須在全域性作用域上,不能巢狀作用域上,當要修改巢狀作用域(enclosing作用域,外層非全域性作用域)中的變數怎麼辦呢,這時就需要nonlocal關鍵字了,程式碼如下:
1 def outer(): 2 count = 10 3 def inner(): 4 nonlocal count 5 count = 20 6 print(count) #20 7 inner() 8 print(count) #20 9 outer()
6、作用域小結
- 變數查詢順序:LEGB,作用域區域性>外層作用域>當前模組中的全域性>python內建作用域;
- 只有函式、類以及模組才能引入新的作用域;
- 對於一個變數,內部作用域先宣告就會覆蓋外部變數,不宣告直接使用,就會使用外部作用域的變數;
- 內部作用域要修改外部作用域變數的值時,全域性變數要使用global關鍵字,巢狀作用域變數要使用nonlocal關鍵字。nonlocal是python3新增的關鍵字,有了這個 關鍵字,就能完美的實現閉包了;
二、函式即物件
在Python中,函式和之前學過的字串、整型、列表等一樣都是物件,而且函式是最高階的物件(物件是類的例項化,可以呼叫相應的方法,函式是包含變數的物件)。如下:
1 def foo(): 2 print('i am the foo') 3 bar() 4 5 def bar(): 6 print('i am the bar') 7 8 foo()
接著,我們再聊一下函式在記憶體的儲存情況:
函式物件的呼叫僅僅比其它物件多了一個()而已!foo,bar與a,b一樣都是個變數名。
既然函式是物件,那麼自然滿足下面兩個條件:
1、函式可以被賦值給其他變數
1 def foo(): 2 print('foo') 3 bar=foo 4 bar() 5 foo() 6 print(id(foo),id(bar)) #1386464801520 1386464801520
2、函式可以被定義在另外一個函式內(作為引數或者返回值),類似於整型、字串等物件
1 # *******函式名作為引數********** 2 def foo(func): 3 print('foo') 4 func() 5 6 def bar(): 7 print('bar') 8 9 foo(bar) 10 11 # *******函式名作為返回值********* 12 def foo(): 13 print('foo') 14 return bar 15 16 def bar(): 17 print('bar') 18 19 b = foo() 20 b()
三、函式的巢狀及閉包