人生苦短,我用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í)一定不要忘記在wrapper中return原來(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)