一.什麼是裝飾器?
實際上裝飾器就是個函數,這個函數可以為其他函數提供附加的功能。
裝飾器在給其他函數添加功能時,不會修改原函數的源代碼,不會修改原函數的調用方式。
高階函數+函數嵌套+閉包 = 裝飾器
1.1什麼是高階函數?
1.1.1函數接收的參數,包涵一個函數名。
1.1.2 函數的傳回值是一個函數名。
其實這兩個條件都很好滿足,下面就是一個高階函數的例子。
def test1():
print "hamasaki ayumi"
def test2(func):
return test1
下面這個代碼就滿足了對高階函數所有的條件,
1.1.3 滿足以上條件的任意一個條件,就可以稱為高階函數。
如果隻是給一個函數添加一個功能,并且不修改原函數的代碼,這一點需求,我們通過高階函數就可以實作,下面是給函數添加一個計算執行時間并不修改函數原有代碼的功能。
#!/usr/bin/python2.7
# -*- coding:utf-8 -*-
import time
def foo():
time.sleep(1)
print "hamasaki ayumi <<a best>> 3.28 now on sale!"
def test(func):
start_time = time.time()
func()
stop_time = time.time()
run_time = stop_time - start_time
print run_time
test(foo)
輸出結果:
hamasaki ayumi <<a best>> 3.28 now on sale!
1.00331807137
通過上面的測試結果,可以看出,沒有修改foo函數的原代碼,還給函數增加了一個顯示執行時間的功能。
雖然使用高階函數給原函數foo增加了功能,但是修改了原函數的調用方式,違反了開放封閉原則。
剛剛說了高階函數接收的參數是一個函數名,我們可以利用這個特性,給函數增加功能(在不修改原代碼的前提下)。
那麼,高階函數的另外一個特點,傳回值是一個函數,如果應用這種特性,就可以做到不改變函數的調用方式了。那我們就來試一下,隻通過高階函數,是否可以完成裝飾器的功能。
def runtime(func):
return func
foo = runtime(foo)
foo()
輸出結果如下:
1.0050868988
從得到的結果來看,foo函數被多執行了一次,具體原因,來分析下。
首先調用了runtime(foo),解釋器解釋到func()的時候,foo函數被執行了一次,runtime函數在return的時候,将foo函數體,return給了foo變量,最後我們在調用foo()的時候,導緻foo函數又被多執行了一次,這顯然不是我們想要的效果。
是以說,“裝飾器”所擁有的功能,單單隻通過高階函數,是無法實作的。
接下來就需要用到剩下的兩個知識點了,分别是函數嵌套和函數閉包。
2.什麼是函數嵌套?
說到函數嵌套,總會有人以為,在一個函數中調用了另外一個函數,就屬于函數嵌套了,就像下面這個例子一樣。
def f1():
print 'f1'
def f2():
print 'f2'
f1()
注意!!這并不是函數嵌套!!
真正的函數嵌套是指,在一個函數中又定義了一個函數。
def f2():
print 'f2'
print locals()
f1()
像這種在一個函數體内又建立了一個函數,這種形式才屬于函數嵌套。
3.什麼是閉包函數?
閉包函數,大概可以解釋為,在一個内部函數中,對外部作用域(這裡的外部作用域指的不是全局作用域!)的變量進行引用,函數内部就可以被了解為是閉包的,下面是一個閉包函數的例子。
a = 1
a = 2
print a
f2()
3.1 關于閉包函數的一些使用注意事項。
3.1.1 第一條要注意的就是!閉包函數内部,預設是不可以修改外部作用域的變量的!!(使用nonlocal關鍵字聲明例外)
示例1:
x = 1
x = 2
print x
print x
最後輸出的結果是:
1
2
在閉包函數中定義的x變量,并沒有影響到外部作用域的x變量。
例子2:
a = 1
def bar():
a = a + 1
return a
return bar
f = foo()
print f()
分析一下這段代碼所存在的問題,在執行foo()函數的時候,python會倒入閉包函數bar,來分析這個作用域的局部變量,其實python在内部規定了,在等号左邊的變量都是局部變量,在閉包函數bar中,a指派在等号的左邊,被python認定為這個a是閉包函數bar中的局部變量,在執行print f()時,程式運作到a=a+1的時候,python會在閉包函數bar中找在等号右邊的a的值,如果找不到,就報錯了。(因為python之前已經把a當做bar閉包函數中的局部變量了。)
那麼接下來,我們融合高階函數+閉包+函數嵌套這三個知識點,來試試看是否可以實作裝飾器的功能。
def func_in():
start_time = time.time()
func()
stop_time = time.time()
run_time = stop_time - start_time
print run_time
return func_in
輸出的結果:
1.00491380692
從結果中可以看出,這次通過上面說的三個知識點,成功初步實作了“裝飾器”的功能。
雖然這個通過函數閉包,函數嵌套,高階函數這三個函數的特性實作了類似裝飾器的功能,但是,還是有個小瑕疵,我們看下上個例子的最後兩行代碼。
如果有很多個函數都要使用前面這個“裝飾器”的話,每個函數在調用之前都要重新指派特别麻煩!
為了更完美的實作裝飾器的功能,還需要引入python中的“文法糖”,也就是一個@符号。
這個“@”符号就是python裝飾器的文法糖,用法就是@後面加上裝飾器的名字,需要使用這個裝飾器,“裝飾”哪個函數,就把這個裝飾器加在函數的上面就可以了。下面是裝飾器文法糖的使用示例。
@runtime
#使用rumtime裝飾器,對foo函數做“裝飾”。
@runtime = foo = runtime(foo)
這兩個文法意義是相同的,隻不過不用在每次調用函數之前,都要對函數重新指派。
4.裝飾器的傳回值。
這隻是實作了基本的裝飾器,看上面的例子,會發現一個問題,在執行foo函數的時候沒有傳回值。
現在有個需求,就是給foo函數添加個傳回值,那我們就先直接在foo函數中retrun一個傳回值出來,看看會有什麼效果。
return "此處是foo函數的傳回值"
a = foo() #将foo函數的傳回值指派給變量a,然後輸出變量a的結果,看看是否能得到我們想要的傳回值。
print a
1.00362682343
None
明明在foo函數中定義了傳回值,但是執行了foo函數所獲的的傳回值依舊是None,這是為什麼?
下面來分析一下原因。
首先,foo函數使用了之前寫的runtime裝飾器@runtime就相當于foo = runtime(foo),看起來運作的是foo函數但實際運作的是runtime函數,runtime函數中,内部包涵的子函數func_in(其實準确的說,最終執行的是runtime函數中的func_in子函數),沒有包含任何傳回值,如果想讓這個foo函數能return出指定的傳回值,就需要從上一層的裝飾器開始下手。
ret = func() #注意看這裡!!這裡将傳進來的foo函數的傳回值賦給了ret變量
return ret #注意看這裡!!這裡将ret變量直接傳回了出去
#foo = runtime(foo)
print foo()
下面是輸出結果:
1.00427913666
此處是foo函數的傳回值
現在通過修改runtime函數内部的func_in子函數的傳回值,得到了想要的結果。
5.裝飾器的變量
依舊用前面的例子舉例,現在foo函數中定義了兩個形參,需要接收兩個參數才可以運作,首先,我們修改foo函數。
def foo(name,age):
print "name:%s age:%s" %(name,age)
下面來調用foo函數來看結果。
print foo(name='suhaozhi',age=18)
print foo(name='suhaozhi',age=18)
TypeError: func_in() takes no arguments (2 given)
結果就是傳回了一個異常。
我們來看下這個異常的内容,說的是func_in函數不需要任何參數,其實仔細想想我們執行foo函數,後 main真正執行的就是裝飾器runtime内部定義的func_in子函數,是以,要給原來的函數傳什麼樣的值,在裝飾器内部也要做相應的修改。
def func_in(*args,**kwargs): 《====#注意看這裡!當調用foo函數時,其實時從這裡開始執行。
ret = func(*args,**kwargs) 《=====#注意看這裡,此處用于接收func_in傳進來的參數。
return ret
下面時輸出結果。
name:suhaozhi age:18
1.00473690033
至于為什麼要使用萬能參數,就是因為裝飾器要修飾的每個函數所傳的變量都是不一樣的,是以使用萬能參數後,裝飾器内部就可以接收各種不同類型不同個數的變量了。
本篇文章隻是對裝飾器的初步了解!關于裝飾器的更多内容都在後面的文章中~未完待續~
本文轉自蘇浩智 51CTO部落格,原文連結:http://blog.51cto.com/suhaozhi/1909347,如需轉載請自行聯系原作者