Python3中裝飾器
什麼是裝飾器?
裝飾器,顧名思義,狹義的可以了解為起到一個裝飾的作用。
即定義一個函數後,想要重新使這個函數增加新的功能,但是不改變原函數的内容。可以想象着進行一個包裝,然後再調用原函數,其實就是調用的包裝後的函數。
下面我們通過執行個體進行闡述:
無參數裝飾器
import time #def caltime(f):就是一個裝飾器,
#裝飾器一般都寫在頂上,當函數要調用裝飾器的時候
#用@進行連接配接 下文中的@caltime
def caltime(f):
def func():
t1 = time.time() #上文導入時間包,t1開始 t2結束 計算f()運算時間
f()
t2 = time.time()
print(t1)
print(t2)
print(t2 - t1)
return func #傳回func函數
@caltime #連接配接上文的裝飾器
def getwater():
print('取了一杯水')
print(pow(2,100)/11*30+23**23)
getwater()
#(輸出) 取了一杯水
2.4337696909561263e+31
1539880939.1847699
1539880939.1857696
0.0009996891021728516
@caltime這個語句相當于 執行 getwater = caltime(getwater),為getwater函數裝飾并傳回func
即getwater=func (實則getwater指向func函數)
是以 getwater()= func()
調用getwater() 實際是調用func()函數。
路徑為下:
getwater()-->進入@caltime-->執行def caltime(f)--
#(@caltime-->執行def func中間并沒有運作func函數,因為并沒有進行調用函數func)
--->return func-->func()-->print(t2 - t1)
有參數的裝飾器
import time
def caltime(flag):
def myfunc(somefunc):
def func(*args, **kwargs):
t1 = time.time()
result = somefunc(*args, **kwargs)
t2 = time.time()
if flag:
print(t2 - t1)
else:
print('笑臉', t2 - t1)
return result
return func
return myfunc
@caltime(flag = True)
def sayhello():
print('hello world')
if __name__ == '__main__':
sayhello()
# 分解:
# 1、calltime(flag = False)
# 必須傳回一個函數,并且帶有一個參數,該參數是函數 =>myfunc(f)
# 2、sayhello傳入到上述傳回的函數參數進行傳遞: 并且傳回一個函數 且傳回的這個函數是被裝飾過的
# sayhello = myfunc(sayhello)
# 3、調用sayhello,實際調用被裝飾過的這個函數
# 4、然後繼續往下,sayhello = myfunc(sayhello)=func(myfunc(sayhello))
#實際上調用sayhello(),就為調用func()
#步驟:caltime(flag = True)-->def caltime(flag)-->return myfunc-->@caltime(flag = True)
# -->def func(*args, **kwargs)-->return func-->@caltime(flag = True)
# -->if __name__ == '__main__': --> sayhello() ---> t1 = time.time() 到return result
#實際上來說:第一個return 為 myfunc, 接下來執行myfunc(sayhello),相當于sayhello = myfunc,
# 而參數 somefunc = sayhello 然後
# 第二個return 為func,并使用原函數名進行接收,相當于:sayhello = myfunc = func,
# 最後當調用sayhello()時,執行上述的func(),被裝飾過的函數
[email protected](fn)裝飾,解決不能調出函數的help說明
# 2 裝飾器裡的@wraps用法
# 我們看這樣一個例子:
import time
def timeit(fn):
def wrapper(*args, **kwargs):
start = time.time()
ret = fn(*args, **kwargs)
print(time.time() - start)
return ret
return wrapper
@timeit
def add(x, y):
'''x + y'''
return x + y
help(add)
#(輸出)Help on function wrapper in module __main__:
# wrapper(*args, **kwargs)
# 我們本意是求add的用法,和add的名字,結果給的不是我們想要的,是因為我們調用add的幫助和名
# 字,實際上調用的是wrapper,是以我們傳回的都是wrapper的help和name。
# 這裡就可以在裝飾器timeit裡面用@wraps(fn)方法,這個方法在标準庫functools裡面。這樣我們就可以
# 得到想要的結果了,檢視幫助是add的幫助,name是add的name:
import time
from functools import wraps
def timeit(fn):
@wraps(fn) # 下面的函數wrapper就內建了傳入的函數的屬性。
def wrapper(*args, **kwargs):
start = time.time()
ret = fn(*args, **kwargs)
print(time.time() - start)
return ret
return wrapper
@timeit
def add(x, y):
'''x + y'''
return x + y
help(add)
#(輸出)Help on function add in module __main__:
#
# add(x, y)
# x + y
4.總結詳解
# 1 不帶參數的裝飾器
# 先看一個沒有用到裝飾器的代碼:
import time
def sleep(n):
time.sleep(n)
start = time.time()
sleep(3)
print(time.time()-start)
#(輸出)3.0056068897247314
# 上述代碼的作用就是算出sleep(3)這個的執行時間。
# 我們可以改寫代碼,使得計算函數執行時間的代碼作為一個函數,接收一個函數,然後計算這個函數的
# 執行時間。
import time
def timeit(fn, *args, **kwargs):
start = time.time()
ret = fn(*args,**kwargs)
print(time.time()-start)
return ret
def sleep(n):
time.sleep(n)
timeit(sleep,3)
# 上面的代碼,必須用timeit來傳入sleep函數和sleep的參數來運作,我們可不可以提前裝飾下要被裝飾的
# 函數,然後調用呢?可以的,如下:
import time
def timeit(fn):
def wrapper(*args, **kwargs):
start = time.time()
ret = fn(*args, **kwargs)
print(time.time()-start)
return ret
return wrapper
def sleep(n):
time.sleep(n)
sleep = timeit(sleep) #提前把sleep函數裝飾了。
sleep(3) # 再調用函數的時候,就是調用被裝飾過的sleep函數了。
#(輸出)3.000748872756958
# Python提供了一種裝飾器的文法糖,可以把這三行:
def sleep(n):
time.sleep(n)
sleep = timeit(sleep)
# 改為下面這三行:
@timeit
def sleep(n):
time.sleep(n)
# 完整版文法糖版的裝飾器示例如下:
import time
def timeit(fn):
def wrapper(*args, **kwargs):
start = time.time()
ret = fn(*args,**kwargs)
print(time.time()-start)
return ret
return wrapper
@timeit
def sleep(n):
time.sleep(n)
sleep(3)
#(輸出)3.004680871963501
# 裝飾器的本質是高階函數,接受一個函數作為參數,并且傳回一個函數。
# 裝飾器通常會傳回一個封裝函數(上述代碼中的wrapper),這個封裝函數在傳入的函數前後做一些事
# 情。
# 詳解上述代碼:
# 我們在timeit裝飾器裡定義了一個封裝函數wrapper,這個wrapper函數在我們真正傳入的函數fn的前後
# 做了一些操作,計算出了fn運作的時間差,并且把函數傳回回去,然後把這個封裝函數wrapper傳回回
# 去,替換原來的fn。
# @timeit 然後定義的函數sleep,意味着sleep = timeit(sleep),即sleep即為傳入的函數,然後經過
# wrapper的封裝,傳回回來,替換原來的sleep。
# 2 裝飾器裡的@wraps用法
# 我們看這樣一個例子:
import time
def timeit(fn):
def wrapper(*args, **kwargs):
start = time.time()
ret = fn(*args, **kwargs)
print(time.time() - start)
return ret
return wrapper
@timeit
def add(x, y):
'''x + y'''
return x + y
help(add)
#(輸出)Help on function wrapper in module __main__:
# wrapper(*args, **kwargs)
# 我們本意是求add的用法,和add的名字,結果給的不是我們想要的,是因為我們調用add的幫助和名
# 字,實際上調用的是wrapper,是以我們傳回的都是wrapper的help和name。
# 這裡就可以在裝飾器timeit裡面用@wraps(fn)方法,這個方法在标準庫functools裡面。這樣我們就可以
# 得到想要的結果了,檢視幫助是add的幫助,name是add的name:
import time
from functools import wraps
def timeit(fn):
@wraps(fn) # 下面的函數wrapper就內建了傳入的函數的屬性。
def wrapper(*args, **kwargs):
start = time.time()
ret = fn(*args, **kwargs)
print(time.time() - start)
return ret
return wrapper
@timeit
def add(x, y):
'''x + y'''
return x + y
help(add)
#(輸出)Help on function add in module __main__:
#
# add(x, y)
# x + y
# 3 帶參數的裝飾器
# 一個程式占用的時間分為使用者時間(包括sleep等待的時間),和cpu時間(cpu時間不包括sleep等待的
# 時間)。
# 下面是cpu時間的例子:
import time
def sleep(n):
time.sleep(3)
start = time.clock()
sleep(3)
print(time.clock() - start)
#(輸出)2.9992514567665083
# 我們想寫一個裝飾器,既可以傳回使用者時間,又可以傳回cpu時間,預設傳回的是使用者時間(可以用一
# 個開關作為參數,來控制傳回的是什麼時間),這時候我們就用到了帶參數的裝飾器:
# 上面是傳回使用者時間的裝飾器。
# 如果後面幾行像下面這樣寫,就是輸出cpu時間了:
@timeit(cpu_time = True) # 傳入cpu_time的參數為True,覆寫掉False預設參數
def fun(n):
time.sleep(n)
return n
print(fun(3))
# 帶參數的裝飾器本質是一個函數,傳入參數,傳回一個新的裝飾器。
# 帶參數的裝飾器隻允許一層,如果像這樣@xxx()()多層了,就會報錯。
import time
def timeit(cpu_time = False):
# 根據傳入的參數,來決定是用time.clock還是time.time
time_func = time.clock if cpu_time else time.time
def dec(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
start = time_func()
ret = fn(*args, **kwargs)
print(time_func() - start)
return ret
return wrapper
return dec
# 下面注釋,等同于@timeit()那段
# def fun(n):
# time.sleep(n)
# return n
# fun = timeit()(fun)
# 其實上面這行,用柯裡化就非常好了解了。
@timeit() # 參數為空,但也是帶了參數了,空參數,傳入的參數為預設參數,等同于@timeit(cpu_time
def fun(n):
time.sleep(n)
return n
fun(3)
# 多個裝飾器裝飾的時候,采用就近原則,然後一層層裝飾,比如:
#@a
#@b
#@c
#def fn():
pass
# 上述等效于a(b(c(fn)))
# 4 裝飾器的應用
# 給函數調用做緩存
from functools import wraps
def memo(fn):
cache = {}
miss = object()
@wraps(fn)
def wrapper(*args):
result = cache.get(args, miss)
if result is miss:
result = fn(*args)
cache[args] = result
return result
return wrapper
@memo
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
# 上面這個例子中,是一個斐波拉契數例的遞歸算法。我們知道,這個遞歸是相當沒有效率的,因為會重
# 複調用。比如:我們要計算fib(5),于是其分解成fib(4) + fib(3),而fib(4)分解成fib(3)+fib(2),fib(3)又分
# 解成fib(2)+fib(1)…… 你可看到,基本上來說,fib(3), fib(2), fib(1)在整個遞歸過程中被調用了兩次。
# 而我們用decorator,在調用函數前查詢一下緩存,如果沒有才調用了,有了就從緩存中傳回值。一下
# 子,這個遞歸從二叉樹式的遞歸成了線性的遞歸。
# 4.2 給函數打日志
# 下面這個示例示範了一個logger的decorator,這個decorator輸出了函數名,參數,傳回值,和運作時
# 間。
from functools import wraps
def logger(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
ts = time.time()
result = fn(*args, **kwargs)
te = time.time()
print("function = {0}".format(fn.__name__))
print(" arguments = {0} {1}".format(args, kwargs))
print(" return = {0}".format(result))
print(" time = %.6f sec" % (te-ts))
return result
return wrapper
@logger
def multipy(x, y):
return x * y
@logger
def sum_num(n):
s = 0
for i in range(n+1):
s += i
return s
print(multipy(2, 10))
print(sum_num(100))
print(sum_num(10000000))