Python裝飾器的詳細用法介紹(程式碼範例)
本篇文章帶給大家的內容是關於Python裝飾器的詳細用法介紹(程式碼範例),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。
在Python中,裝飾器一般用來修飾函數,實現公共功能,達到程式碼重複使用的目的。在函數定義前加上@xxxx,然後函數就注入了某些行為,很神奇!然而,這只是語法糖而已。
場景
假設,有一些工作函數,用來對資料做不同的處理:
def work_bar(data): pass def work_foo(data): pass
我們想要在函數呼叫前/後輸出日誌,怎麼辦?
傻瓜解法
logging.info('begin call work_bar') work_bar(1) logging.info('call work_bar done')
如果有多處程式碼呼叫呢?想想就怕!
函數包裝
傻瓜解法無非是有太多程式碼冗餘,每次函數呼叫都要寫一遍logging
。可以把這部分冗餘邏輯封裝到一個新函數裡:
def smart_work_bar(data): logging.info('begin call: work_bar') work_bar(data) logging.info('call doen: work_bar')
這樣,每次調用smart_work_bar
#即可:
smart_work_bar(1) # ... smart_work_bar(some_data)
通用閉包
看起來挺完美…然而,當work_foo
也有同樣的需要時,還要再實現一次smart_work_foo
嗎?這樣顯然不科學呀!
別急,我們可以用閉包:
def log_call(func): def proxy(*args, **kwargs): logging.info('begin call: {name}'.format(name=func.func_name)) result = func(*args, **kwargs) logging.info('call done: {name}'.format(name=func.func_name)) return result return proxy
這個函數接收一個函數物件(被代理函數)作為參數,傳回一個代理函數。呼叫代理函數時,先輸出日誌,再呼叫被代理函數,呼叫完成後再輸出日誌,最後返回呼叫結果。這樣,不就達到通用化的目的了嗎? ——對於任意被代理函數func
,log_call
皆可輕鬆應付。
smart_work_bar = log_call(work_bar) smart_work_foo = log_call(work_foo) smart_work_bar(1) smart_work_foo(1) # ... smart_work_bar(some_data) smart_work_foo(some_data)
第1
行中,log_call
接收參數work_bar
,傳回一個代理函數proxy
,並賦給smart_work_bar
。在第4
行中,呼叫smart_work_bar
,也就是代理函數proxy
,先輸出日誌,然後呼叫func
也就是 work_bar
,最後再輸出日誌。注意到,代理函數中,func
與傳進去的work_bar
物件緊緊關聯在一起了,這就是閉包。
再提一下,可以覆寫被代理函數名,以smart_
為前綴取新名字還是顯得有些累贅:
work_bar = log_call(work_bar) work_foo = log_call(work_foo) work_bar(1) work_foo(1)
語法糖
#先來看看以下程式碼:
def work_bar(data): pass work_bar = log_call(work_bar) def work_foo(data): pass work_foo = log_call(work_foo)
雖然程式碼沒有什麼冗餘了,但是看是去還是不夠直覺。這時候,語法糖來了~~~
@log_call def work_bar(data): pass
因此,注意一點(劃重點啦),這裡@log_call
的作用只是:告訴 Python
編譯器插入程式碼work_bar = log_call(work_bar)
。
求值裝飾器
先來猜猜裝飾器eval_now
有什麼作用?
def eval_now(func): return func()
看起來好奇怪哦,沒有定義代理函數,算裝飾器嗎?
@eval_now def foo(): return 1 print foo
這段程式碼輸出1
,也就是對函數進行呼叫求值。那麼到底有什麼用呢?直接寫foo = 1
不行麼?在這個簡單的例子,這麼寫當然可以啦。來看一個更複雜的範例-初始化一個日誌物件:
# some other code before... # log format formatter = logging.Formatter( '[%(asctime)s] %(process)5d %(levelname) 8s - %(message)s', '%Y-%m-%d %H:%M:%S', ) # stdout handler stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setFormatter(formatter) stdout_handler.setLevel(logging.DEBUG) # stderr handler stderr_handler = logging.StreamHandler(sys.stderr) stderr_handler.setFormatter(formatter) stderr_handler.setLevel(logging.ERROR) # logger object logger = logging.Logger(__name__) logger.setLevel(logging.DEBUG) logger.addHandler(stdout_handler) logger.addHandler(stderr_handler) # again some other code after...
用eval_now
的方式:
# some other code before... @eval_now def logger(): # log format formatter = logging.Formatter( '[%(asctime)s] %(process)5d %(levelname) 8s - %(message)s', '%Y-%m-%d %H:%M:%S', ) # stdout handler stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setFormatter(formatter) stdout_handler.setLevel(logging.DEBUG) # stderr handler stderr_handler = logging.StreamHandler(sys.stderr) stderr_handler.setFormatter(formatter) stderr_handler.setLevel(logging.ERROR) # logger object logger = logging.Logger(__name__) logger.setLevel(logging.DEBUG) logger.addHandler(stdout_handler) logger.addHandler(stderr_handler) return logger # again some other code after...
兩段程式碼要達到的目的是一樣的,但後者顯然更清晰,頗有程式碼塊的風範。更重要的是,函數呼叫在局部名字空間完成初始化,避免臨時變數(如formatter
等)污染外部的名字空間(如全域)。
帶參數裝飾器
定義一個裝飾器,用來記錄慢函數呼叫:
def log_slow_call(func): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > 1: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxy
第3
、5
行分別在函數呼叫前後取樣目前時間,第7
行計算呼叫耗時,耗時大於一秒輸出一條警告日誌。
@log_slow_call def sleep_seconds(seconds): time.sleep(seconds) sleep_seconds(0.1) # 没有日志输出 sleep_seconds(2) # 输出警告日志
然而,閾值設定總是要視情況決定,不同的函數可能會設定不同的值。如果閾值有辦法參數化就好了:
def log_slow_call(func, threshold=1): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxy
然而,@xxxx
語法糖總是以被裝飾函數為參數呼叫裝飾器,也就是說沒有機會傳遞 threshold
參數。怎麼辦呢? ——用一個閉包封裝threshold
參數:
def log_slow_call(threshold=1): def decorator(func): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxy return decorator @log_slow_call(threshold=0.5) def sleep_seconds(seconds): time.sleep(seconds)
這樣,log_slow_call(threshold=0.5)
呼叫回傳函數decorator
,函數擁有閉包變數threshold
,值為0.5
。 decorator
再裝飾sleep_seconds
。
採用預設閾值,函數呼叫還是不能省略:
@log_slow_call() def sleep_seconds(seconds): time.sleep(seconds)
處女座可能會對第一行這對括號感到不爽,那麼可以這樣改進:
def log_slow_call(func=None, threshold=1): def decorator(func): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxy if func is None: return decorator else: return decorator(func)
這種寫法相容於兩種不同的用法,用法A
預設閾值(無呼叫);用法B
自訂閾值(有呼叫)。
# Case A @log_slow_call def sleep_seconds(seconds): time.sleep(seconds) # Case B @log_slow_call(threshold=0.5) def sleep_seconds(seconds): time.sleep(seconds)
用法A
中,發生的事情是log_slow_call(sleep_seconds)
,也就是func
參數是非空的,這是直接調decorator
進行包裝並返回(閾值是預設的)。
用法B
中,先发生的是log_slow_call(threshold=0.5)
,func
参数为空,直接返回新的装饰器decorator
,关联闭包变量threshold
,值为0.5
;然后,decorator
再装饰函数sleep_seconds
,即decorator(sleep_seconds)
。注意到,此时threshold
关联的值是0.5
,完成定制化。
你可能注意到了,这里最好使用关键字参数这种调用方式——使用位置参数会很丑陋:
# Case B- @log_slow_call(None, 0.5) def sleep_seconds(seconds): time.sleep(seconds)
当然了,函数调用尽量使用关键字参数是一种极佳实践,含义清晰,在参数很多的情况下更是如此。
智能装饰器
上节介绍的写法,嵌套层次较多,如果每个类似的装饰器都用这种方法实现,还是比较费劲的(脑子不够用),也比较容易出错。
假设有一个智能装饰器smart_decorator
,修饰装饰器log_slow_call
,便可获得同样的能力。这样,log_slow_call
定义将变得更清晰,实现起来也更省力啦:
@smart_decorator def log_slow_call(func, threshold=1): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxy
脑洞开完,smart_decorator
如何实现呢?其实也简单:
def smart_decorator(decorator): def decorator_proxy(func=None, **kwargs): if func is not None: return decorator(func=func, **kwargs) def decorator_proxy(func): return decorator(func=func, **kwargs) return decorator_proxy return decorator_proxy
smart_decorator
实现了以后,设想就成立了!这时,log_slow_call
,就是decorator_proxy
(外层),关联的闭包变量decorator
是本节最开始定义的log_slow_call
(为了避免歧义,称为real_log_slow_call
)。log_slow_call
支持以下各种用法:
# Case A @log_slow_call def sleep_seconds(seconds): time.sleep(seconds)
用法A
中,执行的是decorator_proxy(sleep_seconds)
(外层),func
非空,kwargs
为空;直接执行decorator(func=func, **kwargs)
,即real_log_slow_call(sleep_seconds)
,结果是关联默认参数的proxy
。
# Case B # Same to Case A @log_slow_call() def sleep_seconds(seconds): time.sleep(seconds)
用法B
中,先执行decorator_proxy()
,func
及kwargs
均为空,返回decorator_proxy
对象(内层);再执行decorator_proxy(sleep_seconds)
(内层);最后执行decorator(func, **kwargs)
,等价于real_log_slow_call(sleep_seconds)
,效果与用法A
一致。
# Case C @log_slow_call(threshold=0.5) def sleep_seconds(seconds): time.sleep(seconds)
用法C
中,先执行decorator_proxy(threshold=0.5)
,func
为空但kwargs
非空,返回decorator_proxy
对象(内层);再执行decorator_proxy(sleep_seconds)
(内层);最后执行decorator(sleep_seconds, **kwargs)
,等价于real_log_slow_call(sleep_seconds, threshold=0.5)
,阈值实现自定义!
以上是Python裝飾器的詳細用法介紹(程式碼範例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

PHP主要是過程式編程,但也支持面向對象編程(OOP);Python支持多種範式,包括OOP、函數式和過程式編程。 PHP適合web開發,Python適用於多種應用,如數據分析和機器學習。

PHP適合網頁開發和快速原型開發,Python適用於數據科學和機器學習。 1.PHP用於動態網頁開發,語法簡單,適合快速開發。 2.Python語法簡潔,適用於多領域,庫生態系統強大。

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

PHP起源於1994年,由RasmusLerdorf開發,最初用於跟踪網站訪問者,逐漸演變為服務器端腳本語言,廣泛應用於網頁開發。 Python由GuidovanRossum於1980年代末開發,1991年首次發布,強調代碼可讀性和簡潔性,適用於科學計算、數據分析等領域。

VS Code可以在Windows 8上運行,但體驗可能不佳。首先確保系統已更新到最新補丁,然後下載與系統架構匹配的VS Code安裝包,按照提示安裝。安裝後,注意某些擴展程序可能與Windows 8不兼容,需要尋找替代擴展或在虛擬機中使用更新的Windows系統。安裝必要的擴展,檢查是否正常工作。儘管VS Code在Windows 8上可行,但建議升級到更新的Windows系統以獲得更好的開發體驗和安全保障。

VS Code 可用於編寫 Python,並提供許多功能,使其成為開發 Python 應用程序的理想工具。它允許用戶:安裝 Python 擴展,以獲得代碼補全、語法高亮和調試等功能。使用調試器逐步跟踪代碼,查找和修復錯誤。集成 Git,進行版本控制。使用代碼格式化工具,保持代碼一致性。使用 Linting 工具,提前發現潛在問題。

在 Notepad 中運行 Python 代碼需要安裝 Python 可執行文件和 NppExec 插件。安裝 Python 並為其添加 PATH 後,在 NppExec 插件中配置命令為“python”、參數為“{CURRENT_DIRECTORY}{FILE_NAME}”,即可在 Notepad 中通過快捷鍵“F6”運行 Python 代碼。

VS Code 擴展存在惡意風險,例如隱藏惡意代碼、利用漏洞、偽裝成合法擴展。識別惡意擴展的方法包括:檢查發布者、閱讀評論、檢查代碼、謹慎安裝。安全措施還包括:安全意識、良好習慣、定期更新和殺毒軟件。
