人生苦短朋譬,我用Python
__ 文:鄭元春__
1. 一切都是參數(shù)
首先盐茎,在python中什么都可以傳遞,不只是一般的變量徙赢,對象,python還具有面向函數(shù)編程的思想字柠,所以你寫的任何的函數(shù)也可以當(dāng)做是參數(shù)傳遞給另一個函數(shù)。只要記住狡赐,在python中什么都能傳遞窑业。
函數(shù)的作用當(dāng)然是實現(xiàn)功能了,所以作為另一個函數(shù)的參數(shù)的時候就把當(dāng)前函數(shù)的功能帶過去了(只要調(diào)用就行枕屉,和一般調(diào)用的區(qū)別就是這是軟編碼的常柄,并不是硬調(diào)用的),同時還可以增添一些其他的功能搀擂。這就是在不改變原來函數(shù)的情況下西潘,我們只需要編寫額外的功能在另一個函數(shù)中,實現(xiàn)動態(tài)“對功能打補丁”的效果哨颂。
2. 一般的調(diào)用
一般的調(diào)用就是硬編碼調(diào)用喷市,在另一個函數(shù)中直接實現(xiàn)對當(dāng)前函數(shù)的調(diào)用,這種調(diào)用只能實現(xiàn)一對一的調(diào)用威恼。
def func1():
print ("func1")
def func2():
print("before")
func1()
print("after")
我們在func2
函數(shù)中調(diào)用了func1
函數(shù)品姓,但這是硬編碼的,當(dāng)我們有了另外一個函數(shù)func11
的時候箫措,我們要想實現(xiàn)func2
的之前和之后輸出的時候腹备,我們還得寫另外一個函數(shù)來包裝,所以這種方式是一個比較“硬”的編碼方式斤蔓。
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ù)
這里編寫了wrapper函數(shù)之后惧互,直接將需要“打補丁”的函數(shù)當(dāng)做參數(shù)傳遞進去就可以,注意這里的wrapper最后將傳遞進來的函數(shù)又return回去了喇伯,也就是說wrapper只做了增添功能的動作喊儡,最后并返回了原先的函數(shù),這樣我們使用func1=wrapper(func1)
操作的時候稻据,func1還是原來的函數(shù)并沒有做任何的改變艾猜,如果最后沒有這一句return的話买喧,那么使用func1=wrapper(func1)
就對func1進行了重寫,重寫的結(jié)果是func1變成了空匆赃,這不是我們想要的結(jié)果淤毛,所以一定要記住這個返回語句。
我們可以這樣寫算柳,但是這樣寫并不能體現(xiàn)出python的牛逼來低淡,所以python中有一個語法糖是這么實現(xiàn)的。我們稱它為裝飾器(decorator)瞬项。
4. 神奇的decorator
python使用裝飾器將上面的函數(shù)重寫變成了另一種十分簡潔的方式:
def wrapper(func):
print("before")
func()
print("after")
return func
@wrapper
def f()
print("call f")
如果你將上面的腳本保存成python文件并運行你可以看到輸出以下內(nèi)容:
before
call f
after
其實裝飾器此時會將代碼轉(zhuǎn)換成:f=wrapper(f)
蔗蹋,完成的實際上函數(shù)的重寫(增添了wrapper的功能),為了保持原來的函數(shù)的本質(zhì)囱淋,此時一定不要忘記在wrapper
中return
原來的函數(shù)猪杭。但是你會發(fā)現(xiàn)上面的腳本的輸出方式并不是我們預(yù)計的輸出結(jié)果,腳本首先自動輸出了wrapper的結(jié)果妥衣,當(dāng)我們顯示調(diào)用的時候卻只保持了原來函數(shù)的輸出皂吮。我們分析代碼,關(guān)鍵字@
就是一個函數(shù)重寫語句税手,相當(dāng)于f=wrapper(f)
所以此時調(diào)用了wrapper函數(shù)
并在wrapper
內(nèi)部調(diào)用了f函數(shù)
蜂筹。此時完成了f的重寫,還是原來的函數(shù)功能冈止。
我們希望函數(shù)在加了裝飾器之后能夠保持住“打補丁”之后的升級功能狂票,并不是指調(diào)用一次。一個自然而然的做法就是將wrapper函數(shù)在封裝一層熙暴,函數(shù)重寫的時候并不是重寫的原來的函數(shù)闺属,而是“打補丁”之后的函數(shù)姥敛,返回的是一個函數(shù)句柄团赁,如果我們不去顯示調(diào)用的話,不會執(zhí)行任何功能丢烘。
5. 第一個具有實際功能的decorator
我們將裝飾器再封裝一層俱箱。返回的是真實功能的函數(shù)句柄国瓮。這樣既能保持升級后的函數(shù)所有功能,還能防止自動執(zhí)行封裝器狞谱。
def wrapper(func):
def inner():
print("before")
func()
print("after")
return inner
#這里相當(dāng)于 f=wrapper(f)乃摹,此時f重寫為inner的函數(shù)句柄
@wrapper
def f():
print("call f")
f() #如果不顯示的執(zhí)行的話,裝飾器并沒有任何執(zhí)行過程
這樣上面寫的封裝器在內(nèi)部又封裝了一層跟衅,返回是一個inner函數(shù)的句柄孵睬,只要不顯示的調(diào)用就不會執(zhí)行。
同時伶跷,多個裝飾器還可以疊加使用
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ù)的裝飾器
上面的裝飾器的例子是最簡單的例子掰读,并沒有牽扯到任何的參數(shù)信息秘狞。但是一般的函數(shù)都是帶著參數(shù)的,讓我們看看帶參函數(shù)怎么寫裝飾器蹈集。
首先我們分析烁试,之前的wrapper
函數(shù)返回的是inner函數(shù)的函數(shù)句柄,inner函數(shù)會在裝飾器執(zhí)行的時候重寫到實際的函數(shù)中拢肆,所以說可以將原先函數(shù)的參數(shù)交給inner函數(shù)來作為傳遞的橋梁减响。
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ù)再加進去善榛,就"相當(dāng)于"參數(shù)傳遞了辩蛋。
7. 參數(shù)個數(shù)不確定
有的時候我們在寫裝飾器之前是不知道將要裝飾的函數(shù)到底有幾個參數(shù)的呻畸,所以就不能使用基于位置的參數(shù)表示方式移盆,我們使用(*args, **kwargs)來自動適應(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)