深入瞭解Python裝飾器函數

2022-06-24 14:02:24
本篇文章給大家帶來了關於的相關知識,其中主要整理了裝飾器函數的相關問題,包括了裝飾器的形成過程、本質與功能、進階與優化等等內容,下面一起來看一下,希望對大家有幫助。

推薦學習:

假如我寫了一個函數 f

def f():
    print('hello')

之後我想知道這段函數執行所要的時間,這好辦,我只要將程式碼改為如下就行

import time
def f():
    start = time.time()   #獲取程式執行開始的時間
    print('hello')
    end = time.time()     #獲取程式執行結束的時間
    print(end - start)    #得出函數f執行所要時間

f()

但之後我有寫了無數個函數f2,f3……fn,我想知道每個函數執行所需要的時間,那麼如果都像上面一樣改,豈不是很鬧心?還是不行,因為這樣實在是太麻煩了。那怎麼辦呢?於是靈機一動,寫了一個timer函數。。。

import time
def timer(func):
    start = time.time()
    func()
    print(time.time() - start)

def f():
    print('hello')


def f2():
    print('xorld')

timer(f)
timer(f2)

這樣看起來是不是簡單多啦?不管我們寫了多少個函數都可以呼叫這個計時函數來計算函數的執行時間

但是如果我只想用原來的方式f1(),f2(),fn()呼叫了這個函數,函數在原本執行輸出的結果不變的前提下還可以增加計算時間的功能,而不是呼叫timer(f),timer(f2)才能計算時間,這該怎麼辦呢?

看了下面的裝飾器函數你就會知道如何解決這個問題



一、裝飾器 —— 形成過程

以下就是解決上面問題的程式碼的簡單版:

import time

def f():
    print('hello')

def timer(func):
    def inner():
        start = time.time()
        func()
        print(time.time() - start)
    return inner

f = timer(f)
f()

還是這句話我只想用原來的方式f1(),f2(),fn()呼叫了這個函數,函數在原本執行輸出的結果不變的前提下還可以增加計算時間的功能,但我還是要在函數 f 執行前寫 f = timer(f)在這一串程式碼,是不是覺得礙眼?python的開發者也覺得礙眼,所以python的開發者就為我們提供了一句語法糖來解決這個問題!



二、裝飾器 —— 初識語法糖

用@timmer代替f = timer(f),這就是一句語法糖。

import time
def timer(func):
    def inner():
        start = time.time()
        func()
        print(time.time() - start)
    return inner

@timer   #==> 寫著這句話就相當於執行了f = timer(f)
def f():
    print('hello')


f()


三、裝飾器 ——本質與功能

1、本質

裝飾器的本質就是一個閉包函數

2、功能

在不修改原函數及其呼叫方式的情況下對原函數功能進行擴充套件

四、裝飾器 —— 裝飾帶引數,返回值的裝飾器

  • 1、裝飾帶一個引數的函數

剛才我們寫的裝飾器都是裝飾不帶引數的函數,現在要裝飾一個帶引數的函數怎麼辦呢?

import time
def timer(func):
    def inner(a):
        start = time.time()
        func(a)
        print(time.time() - start)
    return inner

@timer
def f(a):
    print(a)

f('hello')
  • 2、裝飾多個帶有不同引數但無返回值的函數

其實裝飾帶參的函數並不是什麼難事,但假如你有兩個函數,需要傳遞的引數不一樣呢,比如 函數func1有兩個引數func1(a ,b),函數func 2只有一個引數func2(a), 且它們都想用這個裝飾器裝飾,做到計算函數執行時間?這怎麼辦呢?那就用下面程式碼。

import time
def timer(func):
    def inner(*args,**kwargs):
        start = time.time()
        re = func(*args,**kwargs)
        print(time.time() - start)
        return re
    return inner

@timer   #==> func1 = timer(func1)
def func1(a,b):
    print('in func1')

@timer   #==> func2 = timer(func2)
def func2(a):
    print('in func2 and get a:%s'%(a))
    return 'fun2 over'

func1('aaaaaa','bbbbbb')
print(func2('aaaaaa'))

輸出結果:
in func1
0.0
in func2 and get a:aaaaaa
0.0
fun2 over
  • 3、裝飾多個帶有不同引數且有返回值的函數

現在引數的問題已經完美的解決了,可是如果你的函數是有返回值的呢?用上面的程式碼你就拿不到返回值了那究竟要如何解決這個問題呢?那就看下面的程式碼吧!

import time
def timer(func):
    def inner(*args,**kwargs):
        start = time.time()
        re = func(*args,**kwargs)
        print(time.time() - start)
        return re
    return inner

@timer   #==> func2 = timer(func2)
def func2(a):
    print('in func2 and get a:%s'%(a))
    return 'fun2 over'

func2('aaaaaa')
print(func2('aaaaaa'))

輸出結果:
in func2 and get a:aaaaaa
0.0
in func2 and get a:aaaaaa
0.0
fun2 over
  • 4、多個裝飾器裝飾同一個函數

有些時候,我們也會用到多個裝飾器裝飾同一個函數的情況。

ef wrapper1(func):   #func ----- f
    def inner1():
        print('wrapper1 ,before func')
        func()
        print('wrapper1 ,after func')
    return inner1

def wrapper2(func):
    def inner2():
        print('wrapper2 ,before func')
        func()
        print('wrapper2 ,after func')
    return inner2

@wrapper2       #f = wrapper2(f) ----->> wrapper2(inner1)  == inner2
@wrapper1       #f = wrapper1(f) = inner
def f():
    print('in f')
f()    #===>>inner2
#多個裝飾器裝飾同一個函數

輸出結果:
wrapper2 ,before func
wrapper1 ,before func
in f
wrapper1 ,after func
wrapper2 ,after func


五、裝飾器 —— 裝飾器進階與優化

  • 1、帶引數的裝飾器

上面那個裝飾器已經非常beautiful了,但是還有一個問題,如果我給程式碼中無數個函數都加了@timer這個語法糖,如果之後我又不想用它了那豈不是又要每個去將它註釋,沒日沒夜忙活3天?豈不是特別麻煩,為了使裝飾器不用時能夠更好的回收而不是一個一個去註釋或者刪除 我們引入帶引數的裝飾器概念

'''
為了使裝飾器不用時能夠更好的回收而不是一個一個去註釋或者刪除
我們引入帶引數的裝飾器概念
'''

import time
'''FLAGE的目的是用它控制裝飾器的開關,
那麼當我們不用的時候就不要一個一個去註釋只需將True改為False就行'''

FLAGE = True
def timmer_out(flag):
    def timmer(func):
        def inner(*args,**kwargs):
            if flag:
                start = time.time()
                ret = func(*args,**kwargs)
                end = time.time()
                print(end - start)
                return ret
            else:
                ret = func(*args, **kwargs)
                return ret
        return inner
    return timmer
@timmer_out(FLAGE)

#timmer_out(FLAGE)
# 也相當於執行  timmer_out(FLAGE)--->>返回timmer———————>>@timmer(wahaha = timmer(wahaha))
def wahaha():
    time.sleep(0.1)         #不休息的話函數執行的太快難以計算時間
    print('wahahahahahaha')

wahaha()

@timmer_out(FLAGE)
def erguotou():
    time.sleep(0.1)         #不休息的話函數執行的太快難以計算時間
    print('erguotoutoutou')

erguotou()

輸出結果:
wahahahahahaha
0.10152268409729004
erguotoutoutou
0.10795140266418457
  • 2、防止函數必要資訊失效

'''
print(wahaha.__name__)      #檢視字串格式的函數名
print(wahaha.__doc__)       #檢視一個函數的註釋
'''
#下面用__name__檢視holiday的函數名

from functools import wraps
def wrapper(func):
    @wraps(func)            #加在最內層函數正上方
    def inner(*args,**kwargs):
        print('在被裝飾的函數執行之前做的事')
        ret = func(*args,**kwargs)
        print('在被裝飾的函數執行之後做的事')
        return ret
    return inner

@wrapper        #holiday = wrapper(holiday)
def holiday(day):
    '''
    這是一個放假通知
    :param day:
    :return:
    '''
    print('全體放假%s天'%day)
    return '好開心'

print(holiday.__name__)
print(holiday.__doc__)
'''
結果是inner和None 但我們想要的是列印holiday的字串格式的函數名和函數的註釋這時該怎麼辦?
解決方法就是  from functools import wraps
使用語法是@wraps(被裝飾的函數名)
'''

輸出結果:
holiday

    這是一個放假通知
    :param day:
    :return:


六、裝飾器 —— 裝飾原則

  • 1、開放封閉原則

1.對原函數的功能擴充套件是開放的

為什麼要對功能擴充套件開放呢?

    對於任何一個程式來說,不可能在設計之初就已經想好了所有的功能並且未來不做任何更新和修改。所以我們必須允許後來擴充套件、新增新功能。

2.對修改是封閉的

為什麼要對修改封閉呢?

就像我們剛剛提到的,因為我們寫的一個函數,很有可能在其他地方已經被匯入使用了,如果這個時候我們對其進行了修改,很有可能影響其他已經正在使用該函數的程式碼。

裝飾器就完美遵循了這個開放封閉原則。這就是學裝飾器的初衷



小結:

  • 1、裝飾器的固定格式(模板)

#格式一

def timer(func):
    def inner(*args,**kwargs):
        '''執行函數之前要做的'''
        re = func(*args,**kwargs)
        '''執行函數之後要做的'''
        return re
    return inner

#格式二

from functools import wraps

def deco(func):
    @wraps(func) #加在最內層函數正上方
    def wrapper(*args,**kwargs):
        return func(*args,**kwargs)
    return wrapper

推薦學習:

以上就是深入瞭解Python裝飾器函數的詳細內容,更多請關注TW511.COM其它相關文章!