再談python裝飾器

人生苦短,我用Python

__ 文:鄭元春__


1. 一切都是參數(shù)

首先,在python中什么都可以傳遞,不只是一般的變量,對(duì)象,python還具有面向函數(shù)編程的思想,所以你寫(xiě)的任何的函數(shù)也可以當(dāng)做是參數(shù)傳遞給另一個(gè)函數(shù)。只要記住,在python中什么都能傳遞。

函數(shù)的作用當(dāng)然是實(shí)現(xiàn)功能了,所以作為另一個(gè)函數(shù)的參數(shù)的時(shí)候就把當(dāng)前函數(shù)的功能帶過(guò)去了(只要調(diào)用就行,和一般調(diào)用的區(qū)別就是這是軟編碼的,并不是硬調(diào)用的),同時(shí)還可以增添一些其他的功能。這就是在不改變?cè)瓉?lái)函數(shù)的情況下,我們只需要編寫(xiě)額外的功能在另一個(gè)函數(shù)中,實(shí)現(xiàn)動(dòng)態(tài)“對(duì)功能打補(bǔ)丁”的效果。


2. 一般的調(diào)用

一般的調(diào)用就是硬編碼調(diào)用,在另一個(gè)函數(shù)中直接實(shí)現(xiàn)對(duì)當(dāng)前函數(shù)的調(diào)用,這種調(diào)用只能實(shí)現(xiàn)一對(duì)一的調(diào)用。

def func1():
    print ("func1")

def func2():
    print("before")
    func1()
    print("after")

我們?cè)?code>func2函數(shù)中調(diào)用了func1函數(shù),但這是硬編碼的,當(dāng)我們有了另外一個(gè)函數(shù)func11的時(shí)候,我們要想實(shí)現(xiàn)func2的之前和之后輸出的時(shí)候,我們還得寫(xiě)另外一個(gè)函數(shù)來(lái)包裝,所以這種方式是一個(gè)比較“硬”的編碼方式。


3. 面向函數(shù)編程思想

既然在python中可以將函數(shù)都當(dāng)做參數(shù)傳遞,那么我們可以嘗試著做如下的改變。

def func1():
    print("func1")

def func2():
    print("func2")

def wrapper(func):
    print("before")
    func()
    print("after")
    return func  #這里的return很重要,要不然調(diào)用一次wrapper之后原先的func就成了空了
func1=wrapper(func1)
func2=wrapper(func2)
f=wrapper(func1)    #這里f就是func1函數(shù)

這里編寫(xiě)了wrapper函數(shù)之后,直接將需要“打補(bǔ)丁”的函數(shù)當(dāng)做參數(shù)傳遞進(jìn)去就可以,注意這里的wrapper最后將傳遞進(jìn)來(lái)的函數(shù)又return回去了,也就是說(shuō)wrapper只做了增添功能的動(dòng)作,最后并返回了原先的函數(shù),這樣我們使用func1=wrapper(func1)操作的時(shí)候,func1還是原來(lái)的函數(shù)并沒(méi)有做任何的改變,如果最后沒(méi)有這一句return的話,那么使用func1=wrapper(func1)就對(duì)func1進(jìn)行了重寫(xiě),重寫(xiě)的結(jié)果是func1變成了空,這不是我們想要的結(jié)果,所以一定要記住這個(gè)返回語(yǔ)句。

我們可以這樣寫(xiě),但是這樣寫(xiě)并不能體現(xiàn)出python的牛逼來(lái),所以python中有一個(gè)語(yǔ)法糖是這么實(shí)現(xiàn)的。我們稱(chēng)它為裝飾器(decorator)。


4. 神奇的decorator

python使用裝飾器將上面的函數(shù)重寫(xiě)變成了另一種十分簡(jiǎn)潔的方式:

def wrapper(func):
    print("before")
    func()
    print("after")
    return func

@wrapper
def f()
    print("call f")

如果你將上面的腳本保存成python文件并運(yùn)行你可以看到輸出以下內(nèi)容:

before
call f
after

其實(shí)裝飾器此時(shí)會(huì)將代碼轉(zhuǎn)換成:f=wrapper(f),完成的實(shí)際上函數(shù)的重寫(xiě)(增添了wrapper的功能),為了保持原來(lái)的函數(shù)的本質(zhì),此時(shí)一定不要忘記在wrapperreturn原來(lái)的函數(shù)。但是你會(huì)發(fā)現(xiàn)上面的腳本的輸出方式并不是我們預(yù)計(jì)的輸出結(jié)果,腳本首先自動(dòng)輸出了wrapper的結(jié)果,當(dāng)我們顯示調(diào)用的時(shí)候卻只保持了原來(lái)函數(shù)的輸出。我們分析代碼,關(guān)鍵字@就是一個(gè)函數(shù)重寫(xiě)語(yǔ)句,相當(dāng)于f=wrapper(f)所以此時(shí)調(diào)用了wrapper函數(shù)并在wrapper內(nèi)部調(diào)用了f函數(shù)。此時(shí)完成了f的重寫(xiě),還是原來(lái)的函數(shù)功能。

我們希望函數(shù)在加了裝飾器之后能夠保持住“打補(bǔ)丁”之后的升級(jí)功能,并不是指調(diào)用一次。一個(gè)自然而然的做法就是將wrapper函數(shù)在封裝一層,函數(shù)重寫(xiě)的時(shí)候并不是重寫(xiě)的原來(lái)的函數(shù),而是“打補(bǔ)丁”之后的函數(shù),返回的是一個(gè)函數(shù)句柄,如果我們不去顯示調(diào)用的話,不會(huì)執(zhí)行任何功能。


5. 第一個(gè)具有實(shí)際功能的decorator

我們將裝飾器再封裝一層。返回的是真實(shí)功能的函數(shù)句柄。這樣既能保持升級(jí)后的函數(shù)所有功能,還能防止自動(dòng)執(zhí)行封裝器。

def wrapper(func):
    def inner():
        print("before")
        func()
        print("after")
    return inner

#這里相當(dāng)于 f=wrapper(f),此時(shí)f重寫(xiě)為inner的函數(shù)句柄
@wrapper
def f():
    print("call f")

f()  #如果不顯示的執(zhí)行的話,裝飾器并沒(méi)有任何執(zhí)行過(guò)程

這樣上面寫(xiě)的封裝器在內(nèi)部又封裝了一層,返回是一個(gè)inner函數(shù)的句柄,只要不顯示的調(diào)用就不會(huì)執(zhí)行。

同時(shí),多個(gè)裝飾器還可以疊加使用

def wrapper1(func):
    def inner():
        print("before wrapper 1")
        func()
        print("after wrapper 2")
    return inner

def wrapper(func):
    def inner():
        print("before wrapper 2")
        func()
        print("after wrapper 2")
    return inner

@wrapper1
@wrapper2
def f():
    print("call f")

f()

[out]:before wrapper 1
      before wrapper 2
      call f
      after wrapper 2
      after wrapper 1

6. 帶參數(shù)函數(shù)的裝飾器

上面的裝飾器的例子是最簡(jiǎn)單的例子,并沒(méi)有牽扯到任何的參數(shù)信息。但是一般的函數(shù)都是帶著參數(shù)的,讓我們看看帶參函數(shù)怎么寫(xiě)裝飾器。

首先我們分析,之前的wrapper函數(shù)返回的是inner函數(shù)的函數(shù)句柄,inner函數(shù)會(huì)在裝飾器執(zhí)行的時(shí)候重寫(xiě)到實(shí)際的函數(shù)中,所以說(shuō)可以將原先函數(shù)的參數(shù)交給inner函數(shù)來(lái)作為傳遞的橋梁。

def wrapper(func):
    def inner(a,b):
        print("before wrapper")
        func(a,b)
        print("after wrapper")
    return inner

@wrapper
def f(a,b):
    print ("call f: a+b=%d"% (a+b) )

f(2,3)

上面的裝飾器相當(dāng)于f=wrapper(f)=inner,然后可以將參數(shù)再加進(jìn)去,就"相當(dāng)于"參數(shù)傳遞了。


7. 參數(shù)個(gè)數(shù)不確定

有的時(shí)候我們?cè)趯?xiě)裝飾器之前是不知道將要裝飾的函數(shù)到底有幾個(gè)參數(shù)的,所以就不能使用基于位置的參數(shù)表示方式,我們使用(*args, **kwargs)來(lái)自動(dòng)適應(yīng)變參和命名參數(shù)

#coding:utf-8
def wrapper(func):
    def inner(*args, **kwargs):
        print("before %s"%func.__name__)
        result=func(*args,**kwargs)
        print("result of %s is %d"%(func.__name__,result))
    return inner

@wrapper
def f1(a,b):
    print("call f1")
    return a+b
@wrapper
def f2(a,b,c):
    print("call f2")
    return a+b+c

f1(1,2)
f2(2,3,4)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Python進(jìn)階框架 希望大家喜歡,點(diǎn)贊哦首先感謝廖雪峰老師對(duì)于該課程的講解 一、函數(shù)式編程 1.1 函數(shù)式編程簡(jiǎn)...
    Gaolex閱讀 5,985評(píng)論 6 53
  • 要點(diǎn): 函數(shù)式編程:注意不是“函數(shù)編程”,多了一個(gè)“式” 模塊:如何使用模塊 面向?qū)ο缶幊蹋好嫦驅(qū)ο蟮母拍睢傩浴?..
    victorsungo閱讀 1,695評(píng)論 0 6
  • 兩本不錯(cuò)的書(shū): 《Python參考手冊(cè)》:對(duì)Python各個(gè)標(biāo)準(zhǔn)模塊,特性介紹的比較詳細(xì)。 《Python核心編程...
    靜熙老師哈哈哈閱讀 3,441評(píng)論 0 80
  • 微信名:洋洋灑灑 作業(yè)1:好好回想一下,小時(shí)候呆呆看過(guò)什么,或者有什么經(jīng)常見(jiàn)到的事情(如廣場(chǎng)舞),挑選一個(gè)印象或一...
    奧利維亞的暢想生活閱讀 453評(píng)論 3 0
  • 1. “我后天有個(gè)酒要喝,你自己吃飯吧?!?“操,那家酒樓的菜難吃的一逼,酒更難喝?!迸肿幼诳蛷d啐了一口瓜子殼,...
    谷谷閱讀 729評(píng)論 13 7

友情鏈接更多精彩內(nèi)容