深入了解python裝飾器
一、裝飾器
1.相關(guān)知識點
- *args:負(fù)責(zé)將多余的位置實參匯總,賦值給args
- **kwargs:負(fù)責(zé)將多余的關(guān)鍵字實參匯總,賦值給kwargs
命名空間與作用域
函數(shù)對象:
- 可以把函數(shù)當(dāng)成參數(shù)傳入
- 可以把函數(shù)當(dāng)做返回值返回
函數(shù)的嵌套定義:在函數(shù)內(nèi)定義函數(shù)
閉包函數(shù):父函數(shù)的返回值為一個函數(shù),被返回的函數(shù)調(diào)用了父函數(shù)的局部變量,且該函數(shù)可以在父函數(shù)外部執(zhí)行
裝飾器:
裝飾器:定義一個為其他函數(shù)添加功能的函數(shù)
為什么要使用裝飾器?
- 開放封閉原則:開放擴(kuò)展功能但封閉源代碼的修改
- 裝飾器就是在不修改裝飾對象源代碼以及調(diào)用方式的前提下,為裝飾對象添加新功能
裝飾器實現(xiàn):
需求:不修改源代碼,計算代碼執(zhí)行時間
源代碼:
import time
def func0(x):
? ? time.sleep(1)
? ? print(x)
func0(0)
# 方案一:實現(xiàn)了計算代碼執(zhí)行時間的功能,但修改了源代碼,不符合要求
def func1(x):
? ? start = time.time()
? ? time.sleep(1)
? ? print(x)
? ? end = time.time()
? ? print('方案一 運行時間:{}'.format(end - start))
func1(1)
# 方案二:滿足要求,但代碼不具備重用性
start = time.time()
func0(2)
end = time.time()
print('方案二 運行時間:{}'.format(end - start))
# 方案三:將方案二封裝為函數(shù),但修改了函數(shù)的調(diào)用方式,不符合要求
def wrapper():
? ? start = time.time()
? ? func0(3)
? ? end = time.time()
? ? print('方案三 運行時間:{}'.format(end - start))
wrapper()
# 方案四:不修改原函數(shù)的調(diào)用方式
def wrapper(x):
? ? start = time.time()
? ? func0(x)
? ? end = time.time()
? ? print('方案四 運行時間:{}'.format(end - start))
wrapper(4)
# 方案五:方案四參數(shù)被寫死,優(yōu)化方案四
def wrapper(*args, **kwargs):
? ? start = time.time()
? ? func0(*args, **kwargs)
? ? end = time.time()
? ? print('方案五 運行時間:{}'.format(end - start))
wrapper(5)
# 方案六:方案五函數(shù)被寫死,優(yōu)化方案五,但修改了源代碼的調(diào)用方式
def outter(a):
? ? def wrapper(*args, **kwargs):
? ? ? ? start = time.time()
? ? ? ? a(*args, **kwargs)
? ? ? ? end = time.time()
? ? ? ? print('方案六 運行時間:{}'.format(end - start))
? ? return wrapper
f = outter(func0)
f(6)
# 方案七:優(yōu)化方案六(outter即為裝飾器,用于裝飾func0)
def outter(a):
? ? def wrapper(*args, **kwargs):
? ? ? ? start = time.time()
? ? ? ? a(*args, **kwargs)
? ? ? ? end = time.time()
? ? ? ? print('方案七 運行時間:{}'.format(end - start))
? ? return wrapper
func0 = outter(func0)?? ?# 偷梁換柱,調(diào)用者無感知
func0(7)運行結(jié)果:
0
1
方案一 運行時間:1.001857042312622
2
方案二 運行時間:1.0040733814239502
3
方案三 運行時間:1.0017154216766357
4
方案四 運行時間:1.007995367050171
5
方案五 運行時間:1.0145602226257324
6
方案六 運行時間:1.0046615600585938
7
方案七 運行時間:1.0094060897827148
2.語法糖
- 使用語法糖,需要將裝飾器放到被裝飾對象前
# 不使用語法糖
import time
def func0(x):
? ? time.sleep(1)
? ? print(x)
# func0(0)
def outter(a):
? ? def wrapper(*args, **kwargs):
? ? ? ? start = time.time()
? ? ? ? a(*args, **kwargs)
? ? ? ? end = time.time()
? ? ? ? print('運行時間:{}'.format(end - start))
? ? return wrapper
func0 = outter(func0)
func0('hello')
# 使用語法糖
import time
def outter(a):
? ? def wrapper(*args, **kwargs):
? ? ? ? start = time.time()
? ? ? ? a(*args, **kwargs)
? ? ? ? end = time.time()
? ? ? ? print('運行時間:{}'.format(end - start))
? ? return wrapper
@outter # 語法糖,等價于該行func0 = outter(func0)
def func0(x):
? ? time.sleep(1)
? ? print(x)
func0('hello')
print(func0.__name__)?運行結(jié)果:
hello
運行時間:1.0050427913665771
wrapper
3.裝飾器模板
?# 裝飾器模板 ?def decorator_name(x): ? ? ?def wrapper(*args, **kwargs): ? ? ? ? ?# ...新添加的功能... ? ? ? ? ?# 下為調(diào)用原函數(shù)的格式 ? ? ? ? ?x(*args, **kwargs) ? ? ?return wrapper @decorator_name ?def func_name(): ? ? ?pass
擴(kuò)展:真正實現(xiàn)偷梁換柱,調(diào)用者無感知
- 不使用裝飾器
?# 不使用裝飾器
import time
def func0(x):
? ? time.sleep(1)
? ? print(x)
func0('hello')
print(func0.__name__) # 查看函數(shù)名?
print(help(func0)) # 查看幫助信息(主要為注釋內(nèi)容)運行結(jié)果:
hello
func0
Help on function func0 in module main:func0(x)
這是函數(shù)None
使用裝飾器:
import time
def outter(a):
? ? def wrapper(*args, **kwargs):
? ? ? ? '''這是裝飾器'''
? ? ? ? start = time.time()
? ? ? ? a(*args, **kwargs)
? ? ? ? end = time.time()
? ? ? ? print('運行時間:{}'.format(end - start))
? ? return wrapper
@outter # func0 = outter(func0)
def func0(x):
? ? '''這是函數(shù)'''
? ? time.sleep(1)
? ? print(x)
func0('hello')
print(func0.__name__)
print(help(func0))運行結(jié)果:
hello
運行時間:1.011878490447998
wrapper
Help on function wrapper in module main:wrapper(*args, **kwargs)
這是裝飾器None
嘔吼,露餡了
- 解決方法,將原函數(shù)的屬性和方法,賦值給裝飾器內(nèi)的wrapper
import time
def outter(a):
? ? def wrapper(*args, **kwargs):
? ? ? ? '''這是裝飾器'''
? ? ? ? start = time.time()
? ? ? ? a(*args, **kwargs)
? ? ? ? end = time.time()
? ? ? ? print('運行時間:{}'.format(end - start))
? ? wrapper.__name__ = a.__name__
? ? wrapper.__doc__ = a.__doc__
? ? return wrapper
@outter # func0 = outter(func0)
def func0(x):
? ? '''這是函數(shù)'''
? ? time.sleep(1)
? ? print(x)
func0('hello')
print(func0.__name__)
print(help(func0))運行結(jié)果:
hello
運行時間:1.0030155181884766
func0
Help on function func0 in module main:func0(*args, **kwargs)
這是函數(shù)None
但是,函數(shù)有很多屬性和方法,一個一個手動修改過于麻煩,甚至可能會遺漏,但python也提供了解決方法
import time
from functools import wraps
def outter(a):
? ? @wraps(a)
? ? def wrapper(*args, **kwargs):
? ? ? ? '''這是裝飾器'''
? ? ? ? start = time.time()
? ? ? ? a(*args, **kwargs)
? ? ? ? end = time.time()
? ? ? ? print('運行時間:{}'.format(end - start))
? ? # wrapper.__name__ = a.__name__
? ? # wrapper.__doc__ = a.__doc__
? ? return wrapper
@outter # func0 = outter(func0)
def func0(x):
? ? '''這是函數(shù)'''
? ? time.sleep(1)
? ? print(x)
func0('hello')
print(func0.__name__)
print(help(func0))運行結(jié)果:
hello
運行時間:1.0114128589630127
func0
Help on function func0 in module main:func0(x)
這是函數(shù)None
4.有參裝飾器
- 裝飾器內(nèi)需要傳入?yún)?shù),但是由于語法糖的限制,裝飾器只能有一個參數(shù),并且該參數(shù)僅用來接收被裝飾對象的內(nèi)存地址,如何傳入其他參數(shù)?
- 解決思路:將裝飾器做成閉包函數(shù)
def outter(db_type):
? ? # 裝飾器auth
? ? def auth(x):
? ? ? ? def wrapper(*args, **kwargs):
? ? ? ? ? ? if db_type == 'file':
? ? ? ? ? ? ? ? name = input('請輸入姓名:')
? ? ? ? ? ? ? ? passwd = input('請輸入密碼:')
? ? ? ? ? ? ? ? if name == 'zhangsan' and passwd == '123':
? ? ? ? ? ? ? ? ? ? x(*args, **kwargs)
? ? ? ? ? ? ? ? ? ? print('基于文件認(rèn)證')
? ? ? ? ? ? ? ? else:
? ? ? ? ? ? ? ? ? ? print('用戶名或密碼錯誤,認(rèn)證失敗')
? ? ? ? ? ? elif db_type == 'mysql':
? ? ? ? ? ? ? ? x(*args, **kwargs)
? ? ? ? ? ? ? ? print('基于數(shù)據(jù)庫認(rèn)證')
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? print('未知認(rèn)證方式,不支持')
? ? ? ? return wrapper
? ? return auth
# 函數(shù)
auth = outter(db_type='file')
@auth
def file():
? ? print('hello')
auth = outter(db_type='mysql')
@auth
def mysql():
? ? print('world')
msg = input('請選擇認(rèn)證方式(1=file|2=mysql):').strip()
if msg =='1':
? ? file()
elif msg == '2':
? ? mysql()
else:
? ? print('不支持')- 將傳入的參數(shù)寫入語法糖
def outter(db_type):
? ? # 裝飾器auth
? ? def auth(x):
? ? ? ? def wrapper(*args, **kwargs):
? ? ? ? ? ? if db_type == 'file':
? ? ? ? ? ? ? ? name = input('請輸入姓名:')
? ? ? ? ? ? ? ? passwd = input('請輸入密碼:')
? ? ? ? ? ? ? ? if name == 'zhangsan' and passwd == '123':
? ? ? ? ? ? ? ? ? ? x(*args, **kwargs)
? ? ? ? ? ? ? ? ? ? print('基于文件認(rèn)證')
? ? ? ? ? ? ? ? else:
? ? ? ? ? ? ? ? ? ? print('用戶名或密碼錯誤,認(rèn)證失敗')
? ? ? ? ? ? elif db_type == 'mysql':
? ? ? ? ? ? ? ? x(*args, **kwargs)
? ? ? ? ? ? ? ? print('基于數(shù)據(jù)庫認(rèn)證')
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? print('未知認(rèn)證方式,不支持')
? ? ? ? return wrapper
? ? return auth
# 函數(shù)
# auth = outter(db_type='file')
# @auth
@outter(db_type='file')
def file():
? ? print('hello')
# auth = outter(db_type='mysql')
# @auth
@outter(db_type='mysql')
def mysql():
? ? print('world')
msg = input('請選擇認(rèn)證方式(1=file|2=mysql):').strip()
if msg =='1':
? ? file()
elif msg == '2':
? ? mysql()
else:
? ? print('不支持')- 模板
# 有參裝飾器模板 def out_decorator_name(x,y,z): ? ? def decorator_name(a): ? ? ? ? def wrapper(*args, **kwargs): ? ? ? ? ? ? # ...新添加的功能... ? ? ? ? ? ? # 下為調(diào)用原函數(shù)的格式 ? ? ? ? ? ? a(*args, **kwargs) ? ? ? ? return wrapper ? ? return decorator_name @out_decorator_name(x,y,z=1) def func_name(): ? ? ?pass
到此這篇關(guān)于深入了解python裝飾器的文章就介紹到這了,更多相關(guān)python裝飾器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python?ORM框架之SQLAlchemy?的基礎(chǔ)用法
這篇文章主要介紹了Python?ORM框架之SQLAlchemy?的基礎(chǔ)用法,ORM全稱?Object?Relational?Mapping對象關(guān)系映射,更多詳細(xì)內(nèi)容需要的小伙伴課題參考下面文章介紹。希望對你的學(xué)習(xí)有所幫助2022-03-03
python算法測試結(jié)果自動保存到excel表格的實現(xiàn)步驟
我們在進(jìn)行算法評估是通常會針對每個樣本的算法處理結(jié)果進(jìn)行統(tǒng)計,例如每個樣本正確預(yù)測數(shù)量、漏檢數(shù)量和誤檢數(shù)量、精度等,本文小編將給大家介紹python算法測試結(jié)果自動保存到excel表格的實現(xiàn)步驟,感興趣的朋友可以參考下2023-12-12
在Python程序中操作文件之isatty()方法的使用教程
這篇文章主要介紹了在Python程序中操作文件之isatty()方法的使用教程,是Python入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-05-05
python目標(biāo)檢測yolo3詳解預(yù)測及代碼復(fù)現(xiàn)
這篇文章主要為大家介紹了python目標(biāo)檢測yolo3詳解預(yù)測及代碼復(fù)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
實踐Python的爬蟲框架Scrapy來抓取豆瓣電影TOP250
這篇文章主要介紹了實踐Python的爬蟲框架Scrapy來抓取豆瓣電影TOP250的過程,文中的環(huán)境基于Windows操作系統(tǒng),需要的朋友可以參考下2016-01-01

