python之裝飾器詳解

2022-03-18 23:08:59 字數 3714 閱讀 8147

這幾天翻看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 time

def 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 time

def 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 time

def 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...