這幾天翻看python語法,看到裝飾器這裡著實卡了一陣,最初認為也就是個函式指標的用法,但仔細研究後發現,不止這麼簡單。
首先很多資料將裝飾器定義為aop的範疇,也就是aspect oriented programming面向切面程式設計的概念,不懂aop不要緊,只要有函式指標的概念,又有巢狀函式的基礎知識,看懂此文一點壓力都沒有。
先說說為什麼要有裝飾器這麼個東西存在吧,這是一種設計模式,較為經典的有插入日誌、效能測試、事務處理等等。概括的講,裝飾器的作用就是為已經存在的物件新增額外的功能。
關於巢狀函式,c/c++程式設計師可能會覺得一頭霧水,因為譚老爺子的書明確指出函式不可以巢狀定義,在c++ primer也僅僅有巢狀類的講解,並不存在巢狀函式一說,但是在一些高階語言中,比如object c,swift都存在巢狀函式主題,感興趣的同學可以去翻一下,尤其是當下熱門swift,巢狀函式和閉包密不可分,可以方便的實現很多邏輯,比如返回乙個函式給呼叫者。
書歸正傳,所謂裝飾器,顧名思義,就是用來裝飾的神器,你有乙個函式a(),覺得它不夠漂亮,但是又不想重寫它,咋辦呢?裝飾器(decorator)這時候就派上用場了。
def funca():print("i am funca()!")
def funcb():
print("i am funcb()")
return funcb
callsth=funca()//呼叫funca得到返回的funcb
callsth()//相當於呼叫funcb
值得說明一點,在python裡只要你不怕蛋疼,你可以任意巢狀,哪怕你巢狀個百十層直到棧溢位都沒問題。
如下:
def ppp():print("return vlaue")
def first():
a=10// a被巢狀內的函式捕獲!
print("the first layer")
def second():
print("the second layer %d" % a)
def third():
print("the third layer %d " % a)
def forth():
print("the forth layer")
return ppp()
return forth
return third
return second
aaa=first()()()//得到forth()
aaa()
關於巢狀函式是這樣執行的,先執行最外層的first(),返回second然後發現後面跟著(),那繼續返回third,以此類推,聰明如你,肯定明白了。巢狀函式就這樣,簡單到爆。如果到這裡諸君理解了,裝飾器基本上也就理解了。
下面正式開始裝飾器的概念(開啟摘抄模式):
裝飾器的定義很是抽象,我們來看乙個小例子。
def foo():print 'in foo()'
foo()
這是乙個很無聊的函式沒錯。但是突然有乙個更無聊的人,我們稱呼他為b君,說我想看看執行這個函式用了多長時間,好吧,那麼我們可以這樣做:
import timedef foo():
start = time.clock()
print 'in foo()'
end = time.clock()
print 'used:', end - start
foo()
很好,功能看起來無懈可擊。可是蛋疼的b君此刻突然不想看這個函式了,他對另乙個叫foo2的函式產生了更濃厚的興趣。
怎麼辦呢?如果把以上新增加的**複製到foo2裡,這就犯了大忌了~複製什麼的難道不是最討厭了麼!而且,如果b君繼續看了其他的函式呢?
以不變應萬變:還記得嗎,函式在python中是一等公民,那麼我們可以考慮重新定義乙個函式timeit,將foo的引用傳遞給他,然後在timeit中呼叫foo並進行計時,這樣,我們就達到了不改動foo定義的目的,而且,不論b君看了多少個函式,我們都不用去修改函式定義了!
import timedef foo():
print 'in foo()'
def timeit(func):
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
timeit(foo)
看起來邏輯上並沒有問題,一切都很美好並且運作正常!……等等,我們似乎修改了呼叫部分的**。原本我們是這樣呼叫的:foo(),修改以後變成了:timeit(foo)。這樣的話,如果foo在n處都被呼叫了,你就不得不去修改這n處的**。或者更極端的,考慮其中某處呼叫的**無法修改這個情況,比如:這個函式是你交給別人使用的。
既然如此,我們就來想想辦法不修改呼叫的**;如果不修改呼叫**,也就意味著呼叫foo()需要產生呼叫timeit(foo)的效果。我們可以想到將timeit賦值給foo,但是timeit似乎帶有乙個引數……想辦法把引數統一吧!如果timeit(foo)不是直接產生呼叫效果,而是返回乙個與foo引數列表一致的函式的話……就很好辦了,將timeit(foo)的返回值賦值給foo,然後,呼叫foo()的**完全不用修改!
乙個真正意義上的裝飾器誕生了:
#-*- coding: utf-8 -*-import time
def foo():
print 'in foo()'
# 定義乙個計時器,傳入乙個,並返回另乙個附加了計時功能的方法
def timeit(func):
# 定義乙個內嵌的包裝函式,給傳入的函式加上計時功能的包裝
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
# 將包裝後的函式返回
foo = timeit(foo)
foo()
樣,乙個簡易的計時器就做好了!我們只需要在定義foo以後呼叫foo之前,加上foo = timeit(foo),就可以達到計時的目的,這也就是裝飾器的概念,看起來像是foo被timeit裝飾了。在在這個例子中,函式進入和退出時需要計時,這被稱為乙個橫切面(aspect),這種程式設計方式被稱為面向切面的程式設計(aspect-oriented programming)。與傳統程式設計習慣的從上往下執行方式相比較而言,像是在函式執行的流程中橫向地插入了一段邏輯。在特定的業務領域裡,能減少大量重複**。面向切面程式設計還有相當多的術語,這裡就不多做介紹,感興趣的話可以去找找相關的資料。
上面這段**看起來似乎已經不能再精簡了,python於是提供了乙個語法糖來降低字元輸入量。
import timedef timeit(func):
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
@timeit// syntax sugar 是也,在定義上加上這一行與另外寫foo = timeit(foo)完全等價,千萬不要以為@有另外的魔力。除了字元輸入少了一些,還有乙個額外的好處:這樣看上去更有裝飾器的感覺。
def foo():
print 'in foo()'
foo()
相關資料:
python裝飾器 python 裝飾器詳解
def outer x def inner y return x y return inner print outer 6 5 11 如 所示,在outer函式內,又定義了乙個inner函式,並且inner函式又引用了外部函式outer的變數x,這就是乙個閉包了。在輸出時,outer 6 5 第乙個...
python裝飾器詳解 python裝飾器詳解
按照 python 的程式設計原則,當乙個函式被定義後,如要修改或擴充套件其功能應盡量避免直接修改函式定義的 段,否則該函式在其他地方被呼叫時將無法正常執行。因此,當需要修改或擴充套件已被定義的函式的功能而不希望直接修改其 時,可以使用裝飾器。先來看乙個簡單的例子 def func1 functio...
詳解Python裝飾器
裝飾器的難點 在梳理了裝飾器的整個內容之後,我認為難點不是裝飾器本身,而是直接呼叫被裝飾的函式,讓人無法理解背後究竟發生了什麼。一 引出裝飾器概念 引入問題 定義了乙個函式,想在執行時動態的增加功能,又不想改動函式本身的 示例 希望對下列函式呼叫增加log功能,列印出函式呼叫 def f1 x re...