天天看點

#Python3中裝飾器@Python3中裝飾器

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))