詳解如何利用Python裝飾器優(yōu)化代碼
本文將帶你深入探討裝飾器的應(yīng)用,包括計(jì)時器裝飾器和緩存裝飾器等的實(shí)現(xiàn)。通過這些案例,我們可以看到裝飾器的強(qiáng)大和靈活,它們可以幫助我們優(yōu)化代碼,提高性能,讓我們的程序更加健壯和高效。
你是不是經(jīng)常發(fā)現(xiàn)自己寫 Python 代碼很冗余?或者已經(jīng)寫了一些簡潔的代碼,但是正常運(yùn)行時卻會遇到性能問題?那么,Python 裝飾器就是你的救星!本文將帶你深入探討裝飾器的應(yīng)用,包括計(jì)時器裝飾器和緩存裝飾器的實(shí)現(xiàn)。
什么是裝飾器
先來了解一下什么是裝飾器。
裝飾器是 Python 的一個重要特性,它允許你將一個函數(shù)作為參數(shù)傳遞給另一個函數(shù),并返回一個新的函數(shù),而不對原始函數(shù)進(jìn)行修改。這使得你可以在不改變代碼本身的情況下,動態(tài)地修改函數(shù)的行為。下面我們會通過具體的案例來進(jìn)一步解釋這個概念。
裝飾器的應(yīng)用
直接通過案例來理解和使用裝飾器。
計(jì)時器裝飾器
計(jì)時器裝飾器可以幫助你在代碼運(yùn)行時自動計(jì)時,以便你了解代碼的性能。下面是一個例子:
import?time #?定義計(jì)時器裝飾器 def?timer_decorator(func): ????#?定義內(nèi)部包裝函數(shù),用于接收任意數(shù)量的位置參數(shù)和關(guān)鍵字參數(shù) ????def?wrapper(*args,?**kwargs): ????????#?記錄函數(shù)運(yùn)行開始時間 ????????start_time?=?time.time() ????????#?調(diào)用原始函數(shù)并將結(jié)果存儲在result中 ????????result?=?func(*args,?**kwargs) ????????#?記錄函數(shù)運(yùn)行結(jié)束時間 ????????end_time?=?time.time() ????????#?計(jì)算函數(shù)運(yùn)行時間并打印結(jié)果 ????????print(f"函數(shù)?{func.__name__}?運(yùn)行時間為?{end_time?-?start_time}?秒") ????????#?返回原始函數(shù)的結(jié)果 ????????return?result ????#?返回包裝函數(shù) ????return?wrapper @timer_decorator def?slow_function(): ????time.sleep(2) ????print("使用?timer_decorator?計(jì)算下?slow_function?函數(shù)的運(yùn)行時長") slow_function()
在上面的例子中,我們定義了一個計(jì)時器裝飾器 timer_decorator
,并將它應(yīng)用到一個簡單函數(shù) slow_function
上。當(dāng)我們調(diào)用 slow_function
時,計(jì)時器會自動開始計(jì)時,并在函數(shù)執(zhí)行完畢后輸出函數(shù)的運(yùn)行時間。
緩存裝飾器
緩存裝飾器可以幫助你避免重復(fù)計(jì)算,以提高代碼的性能。下面是一個例子,使用了一個緩存裝飾器來優(yōu)化遞歸斐波那契數(shù)列計(jì)算:
#?定義緩存裝飾器 def?cache_decorator(func): ????#?創(chuàng)建一個字典來存儲緩存的結(jié)果 ????cache?=?dict() ????#?定義內(nèi)部包裝函數(shù),用于接收任意數(shù)量的位置參數(shù) ????def?wrapper(*args): ????????#?檢查當(dāng)前參數(shù)是否在緩存中 ????????if?args?in?cache: ????????????#?如果在緩存中,則從緩存中獲取結(jié)果并打印提示信息 ????????????print(f"從緩存中獲取?{args}?的結(jié)果") ????????????return?cache[args] ????????#?如果不在緩存中,則調(diào)用原始函數(shù)計(jì)算結(jié)果 ????????result?=?func(*args) ????????#?將計(jì)算結(jié)果存儲到緩存中,并打印提示信息 ????????cache[args]?=?result ????????print(f"計(jì)算?{args}?的結(jié)果并將其存入緩存") ????????#?返回計(jì)算結(jié)果 ????????return?result ????#?返回包裝函數(shù) ????return?wrapper #?使用緩存裝飾器修飾fibonacci函數(shù) @cache_decorator def?fibonacci(n): ????if?n?<?2: ????????return?n ????else: ????????return?fibonacci(n-1)?+?fibonacci(n-2) print(fibonacci(3)) print("*****************") print(fibonacci(5))
在上面的例子中,我們定義了一個緩存裝飾器 cache_decorator
,并將它應(yīng)用到一個計(jì)算斐波那契數(shù)列的函數(shù) fibonacci
上。當(dāng)我們多次調(diào)用 fibonacci
函數(shù)時,緩存裝飾器會自動檢查緩存中是否已經(jīng)計(jì)算了該值,如果已經(jīng)計(jì)算則直接返回緩存中的值,否則進(jìn)行計(jì)算并將結(jié)果存入緩存。
類型檢查裝飾器
類型檢查裝飾器可以幫助你在函數(shù)調(diào)用時自動檢查參數(shù)類型,以便你避免傳入錯誤的參數(shù)。下面是一個例子:
#?定義類型檢查裝飾器 def?type_check_decorator(func): ????#?定義內(nèi)部包裝函數(shù),用于接收任意數(shù)量的位置參數(shù)和關(guān)鍵字參數(shù) ????def?wrapper(*args,?**kwargs): ????????#?遍歷位置參數(shù) ????????for?i,?arg?in?enumerate(args): ????????????#?如果參數(shù)不是字符串類型,拋出TypeError異常 ????????????if?not?isinstance(arg,?str): ????????????????raise?TypeError(f"第?{i+1}?個參數(shù)值?{arg}?必須是?str?類型") ????????#?遍歷關(guān)鍵字參數(shù) ????????for?key,?value?in?kwargs.items(): ????????????#?如果關(guān)鍵字參數(shù)的值不是字符串類型,拋出TypeError異常 ????????????if?not?isinstance(value,?str): ????????????????raise?TypeError(f"關(guān)鍵字參數(shù)?{key}?必須是?str?類型") ????????#?參數(shù)檢查通過后,調(diào)用原始函數(shù)并返回結(jié)果 ????????return?func(*args,?**kwargs) ????#?返回包裝函數(shù) ????return?wrapper #?使用類型檢查裝飾器修飾concat_strings函數(shù) @type_check_decorator def?concat_strings(*strings,?sep="?"): ????return?sep.join(strings)
在上面的例子中,我們定義了一個類型檢查裝飾器 type_check_decorator
,并將它應(yīng)用到一個將多個字符串拼接為一個字符串的函數(shù) concat_strings
上。當(dāng)我們調(diào)用 concat_strings
時,類型檢查裝飾器會自動檢查參數(shù)類型,并在參數(shù)類型錯誤時拋出異常。
日志裝飾器
日志裝飾器可以幫助你在代碼執(zhí)行時自動記錄日志,以便你了解代碼的執(zhí)行情況。下面是一個例子:
import?logging #?定義日志裝飾器 def?log_decorator(func): ????#?配置日志記錄器,將日志記錄到文件example.log,日志級別為INFO ????logging.basicConfig(filename="example.log",?level=logging.INFO) ????#?定義內(nèi)部包裝函數(shù),用于接收任意數(shù)量的位置參數(shù)和關(guān)鍵字參數(shù) ????def?wrapper(*args,?**kwargs): ????????#?記錄函數(shù)調(diào)用信息到日志文件中 ????????logging.info(f"Calling?function?{func.__name__}") ????????#?調(diào)用原始函數(shù)并將結(jié)果存儲在result中 ????????result?=?func(*args,?**kwargs) ????????#?記錄函數(shù)返回值信息到日志文件中 ????????logging.info(f"Function?{func.__name__}?returned?{result}") ????????#?返回原始函數(shù)的結(jié)果 ????????return?result ????#?返回包裝函數(shù) ????return?wrapper #?使用日志裝飾器修飾add函數(shù) @log_decorator def?add(a,?b): ????return?a?+?b #?調(diào)用add函數(shù)并打印結(jié)果 print(add(1,?2))
在上面的例子中,我們定義了一個日志裝飾器 log_decorator
,并將它應(yīng)用到一個加法函數(shù) add
上。當(dāng)我們調(diào)用 add
時,日志裝飾器會自動記錄日志,并將日志信息寫入到指定的文件中。
授權(quán)裝飾器
授權(quán)裝飾器可以幫助你在函數(shù)調(diào)用時自動檢查用戶權(quán)限,以便你避免未授權(quán)的用戶訪問敏感數(shù)據(jù)。下面是一個例子:
#?定義一個高階函數(shù),接受一個所需角色列表作為參數(shù) def?roles_required(required_roles): ????#?定義授權(quán)裝飾器 ????def?authorization_decorator(func): ????????#?定義包裝函數(shù),用于接收任意數(shù)量的位置參數(shù)和關(guān)鍵字參數(shù) ????????def?wrapper(*args,?**kwargs): ????????????#?獲取用戶角色,默認(rèn)為"guest" ????????????user_role?=?kwargs.get("user_role",?"guest") ????????????#?檢查用戶角色是否在所需角色列表中 ????????????if?user_role?not?in?required_roles: ????????????????#?如果不在列表中,拋出一個?PermissionError?異常 ????????????????raise?PermissionError(f"訪問被拒絕:?需要?{',?'.join(required_roles)}?其中之一的角色") ????????????#?如果用戶角色在所需角色列表中,調(diào)用原始函數(shù)并返回結(jié)果 ????????????return?func(*args,?**kwargs) ????????#?返回包裝函數(shù) ????????return?wrapper ????#?返回授權(quán)裝飾器 ????return?authorization_decorator #?使用?roles_required?裝飾器,允許?admin?和?user?角色的用戶訪問受保護(hù)的功能 @roles_required(["admin",?"user"]) def?protected_function(user_role="guest"): ????print("受保護(hù)的功能已成功執(zhí)行") #?嘗試使用?guest?角色訪問受保護(hù)的功能 try: ????protected_function(user_role="guest") except?PermissionError?as?e: ????print(e) #?嘗試使用?user?角色訪問受保護(hù)的功能 try: ????protected_function(user_role="user") except?PermissionError?as?e: ????print(e) #?嘗試使用?admin?角色訪問受保護(hù)的功能 try: ????protected_function(user_role="admin") except?PermissionError?as?e: ????print(e)
在上面的例子中,我們首先定義了一個名為roles_required
的高階函數(shù),它接受一個required_roles
列表參數(shù)。該高階函數(shù)返回一個名為authorization_decorator
的裝飾器,該裝飾器定義了一個名為wrapper
的內(nèi)部函數(shù),用于檢查用戶角色是否在所需角色列表中。如果用戶角色在列表中,裝飾器將調(diào)用原始函數(shù)并返回結(jié)果;否則,它將拋出一個PermissionError
異常。
我們使用@roles_required(["admin", "user"])裝飾器來修飾protected_function
,確保只有具有admin或user角色的用戶可以訪問該功能。然后,我們嘗試使用不同角色的用戶訪問受保護(hù)的功能,以驗(yàn)證裝飾器的功能。
拓展
在上面的案例中,我們用到了兩個額外功能:高階函數(shù)和包裝器。這兩個概念在Python裝飾器的實(shí)現(xiàn)中起到了關(guān)鍵作用。接下來,我們詳細(xì)介紹它們的作用和應(yīng)用:
高階函數(shù)
高階函數(shù)是指接收一個或多個函數(shù)作為參數(shù)并返回一個新函數(shù)的函數(shù)。在我們的示例中,roles_required
函數(shù)是一個高階函數(shù)。它接收一個所需角色列表作為參數(shù),并返回一個名為authorization_decorator
的裝飾器函數(shù)。
高階函數(shù)在函數(shù)式編程中具有重要作用,因?yàn)樗鼈冊试S我們將函數(shù)作為參數(shù)傳遞,從而提高代碼的靈活性和復(fù)用性。
高階函數(shù)的應(yīng)用:
- 實(shí)現(xiàn)代碼復(fù)用和模塊化
- 簡化代碼結(jié)構(gòu),提高代碼可讀性
- 實(shí)現(xiàn)函數(shù)式編程范式,支持函數(shù)作為參數(shù)傳遞和返回值
包裝器
包裝器(Wrapper)是一個用于修改其他函數(shù)行為的函數(shù)。在我們的示例中,wrapper函數(shù)是一個包裝器。它接收任意數(shù)量的位置參數(shù)和關(guān)鍵字參數(shù),然后根據(jù)特定邏輯(在這個例子中是檢查用戶角色)來決定是否調(diào)用原始函數(shù)(即被裝飾的函數(shù))。
包裝器的應(yīng)用:
- 修改或增強(qiáng)原始函數(shù)的行為,而無需修改原始函數(shù)的代碼
- 實(shí)現(xiàn)代碼的解耦,將不同功能分開
- 提高代碼的可讀性和可維護(hù)性
在裝飾器中,高階函數(shù)和包裝器共同發(fā)揮作用。高階函數(shù)負(fù)責(zé)接收參數(shù)并生成裝飾器,而裝飾器內(nèi)部的包裝器負(fù)責(zé)根據(jù)特定邏輯對原始函數(shù)進(jìn)行修改或增強(qiáng)。這種組合提供了一種靈活且強(qiáng)大的方式來擴(kuò)展和修改函數(shù)的行為,同時保持代碼的模塊化和易于維護(hù)。
總結(jié)
本文介紹了 Python 裝飾器的基本概念和幾種常見的應(yīng)用場景:計(jì)時器裝飾器和緩存裝飾器等。通過這些案例,我們可以看到裝飾器的強(qiáng)大和靈活,它們可以幫助我們優(yōu)化代碼,提高性能,讓我們的程序更加健壯和高效。
到此這篇關(guān)于詳解如何利用Python裝飾器優(yōu)化代碼的文章就介紹到這了,更多相關(guān)Python裝飾器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python 用opencv調(diào)用訓(xùn)練好的模型進(jìn)行識別的方法
今天小編就為大家分享一篇python 用opencv調(diào)用訓(xùn)練好的模型進(jìn)行識別的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-12-12關(guān)于Python如何避免循環(huán)導(dǎo)入問題詳解
在大型的Python工程中,由于架構(gòu)設(shè)計(jì)不當(dāng),可能會出現(xiàn)模塊間相互引用的情況。下面這篇文章主要給大家介紹了關(guān)于如何避免Python的循環(huán)導(dǎo)入問題的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。2017-09-09Pygame庫200行代碼實(shí)現(xiàn)簡易飛機(jī)大戰(zhàn)
本文主要介紹了Pygame庫200行代碼實(shí)現(xiàn)簡易飛機(jī)大戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12Python中的TfidfVectorizer參數(shù)使用解析
這篇文章主要介紹了Python中的TfidfVectorizer參數(shù)使用解析,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11python學(xué)習(xí)筆記之調(diào)用eval函數(shù)出現(xiàn)invalid syntax錯誤問題
python是一門多種用途的編程語言,時常扮演腳本語言的角色。一般來說,python可以定義為面向?qū)ο蟮哪_本語言,這個定義把面向?qū)ο蟮闹С趾兔嫦蚰_本語言的角色融合在一起。很多時候,人們常常喜歡用“腳本”和不是語言來描述python的代碼文件。2015-10-10Python如何使用標(biāo)準(zhǔn)庫tmpfile庫創(chuàng)建臨時文件
這篇文章主要介紹了Python如何使用標(biāo)準(zhǔn)庫tmpfile庫創(chuàng)建臨時文件問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-02-02Python利用多線程同步鎖實(shí)現(xiàn)多窗口訂票系統(tǒng)(推薦)
這篇文章主要介紹了Python利用多線程同步鎖實(shí)現(xiàn)多窗口訂票系統(tǒng),主要是利用threading.lock()通過實(shí)例代碼相結(jié)合給大家講解的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2019-12-12從訓(xùn)練好的tensorflow模型中打印訓(xùn)練變量實(shí)例
今天小編就為大家分享一篇從訓(xùn)練好的tensorflow模型中打印訓(xùn)練變量實(shí)例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-01-01