程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

【Python基礎篇007】帶你超深度剖析Python裝飾器函數

編輯:Python

 

目錄

前言:

一、裝飾器 —— 形成過程

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

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

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

1、裝飾帶一個參數的函數

2、裝飾多個帶有不同參數但無返回值的函數

3、裝飾多個帶有不同參數且有返回值的函數

4、多個裝飾器裝飾同一個函數

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

1、帶參數的裝飾器

2、防止函數必要信息失效

六、裝飾器 —— 裝飾原則

1、開放封閉原則

小結:

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



前言:

假如我寫了一個函數 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

最後感謝各位能夠看到這裡:

在魯迅一篇未發表的文章中說過:“代碼看懂了不是懂一定要自己實際操作哇這樣才能更好的理解和吸收。”
最後來一句:一個人可以在任何他懷有無限熱忱的事情上成功,讓我們一起進步吧


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved