本文目錄一覽:
- 1、python裝飾器有什麼用
- 2、python中更優雅的記錄日誌
- 3、Loguru:Python 日誌終極解決方案
- 4、python-復盤-裝飾器應用場景大總結
- 5、Python筆記:Python裝飾器
python裝飾器有什麼用
先來個形象比方
內褲可以用來遮羞,但是到了冬天它沒法為我們防風禦寒,聰明的人們發明了長褲,有了長褲後寶寶再也不冷了,裝飾器就像我們這裡說的長褲,在不影響內褲作用的前提下,給我們的身子提供了保暖的功效。
再回到我們的主題
裝飾器本質上是一個Python函數,它可以讓其他函數在不需要做任何代碼變動的前提下增加額外功能,裝飾器的返回值也是一個函數對象。它經常用於有切面需求的場景,比如:插入日誌、性能測試、事務處理、緩存、權限校驗等場景。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量與函數功能本身無關的雷同代碼並繼續重用。概括的講,裝飾器的作用就是為已經存在的對象添加額外的功能。
先來看一個簡單例子:
def foo():
print(‘i am foo’)
現在有一個新的需求,希望可以記錄下函數的執行日誌,於是在代碼中添加日誌代碼:
def foo():
print(‘i am foo’)
logging.info(“foo is running”)
bar()、bar2()也有類似的需求,怎麼做?再寫一個logging在bar函數里?這樣就造成大量雷同的代碼,為了減少重複寫代碼,我們可以這樣做,重新定義一個函數:專門處理日誌 ,日誌處理完之後再執行真正的業務代碼
def use_logging(func):
logging.warn(“%s is running” % func.__name__)
func()def bar():
print(‘i am bar’)use_logging(bar)
邏輯上不難理解,
但是這樣的話,我們每次都要將一個函數作為參數傳遞給use_logging函數。而且這種方式已經破壞了原有的代碼邏輯結構,之前執行業務邏輯時,執行運行bar(),但是現在不得不改成use_logging(bar)。那麼有沒有更好的方式的呢?當然有,答案就是裝飾器。
簡單裝飾器
def use_logging(func):
def wrapper(*args, **kwargs):
logging.warn(“%s is running” % func.__name__)
return func(*args, **kwargs)
return wrapperdef bar():
print(‘i am bar’)bar = use_logging(bar)bar()
函數use_logging就是裝飾器,它把執行真正業務方法的func包裹在函數裏面,看起來像bar被use_logging裝飾了。在這個例子中,函數進入和退出時
,被稱為一個橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。
@符號是裝飾器的語法糖,在定義函數的時候使用,避免再一次賦值操作
def use_logging(func):
def wrapper(*args, **kwargs):
logging.warn(“%s is running” % func.__name__)
return func(*args)
return wrapper@use_loggingdef foo():
print(“i am foo”)@use_loggingdef bar():
print(“i am bar”)bar()
如上所示,這樣我們就可以省去bar =
use_logging(bar)這一句了,直接調用bar()即可得到想要的結果。如果我們有其他的類似函數,我們可以繼續調用裝飾器來修飾函數,而不用重複修改函數或者增加新的封裝。這樣,我們就提高了程序的可重複利用性,並增加了程序的可讀性。
裝飾器在Python使用如此方便都要歸因於Python的函數能像普通的對象一樣能作為參數傳遞給其他函數,可以被賦值給其他變量,可以作為返回值,可以被定義在另外一個函數內。
帶參數的裝飾器
裝飾器還有更大的靈活性,例如帶參數的裝飾器:在上面的裝飾器調用中,比如@use_logging,該裝飾器唯一的參數就是執行業務的函數。裝飾器的語法允許我們在調用時,提供其它參數,比如@decorator(a)。這樣,就為裝飾器的編寫和使用提供了更大的靈活性。
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == “warn”:
logging.warn(“%s is running” % func.__name__)
return func(*args)
return wrapper
return decorator@use_logging(level=”warn”)def foo(name=’foo’):
print(“i am %s” % name)foo()
上面的use_logging是允許帶參數的裝飾器。它實際上是對原有裝飾器的一個函數封裝,並返回一個裝飾器。我們可以將它理解為一個含有參數的閉包。當我
們使用@use_logging(level=”warn”)調用的時候,Python能夠發現這一層的封裝,並把參數傳遞到裝飾器的環境中。
類裝飾器
再來看看類裝飾器,相比函數裝飾器,類裝飾器具有靈活度大、高內聚、封裝性等優點。使用類裝飾器還可以依靠類內部的\_\_call\_\_方法,當使用 @ 形式將裝飾器附加到函數上時,就會調用此方法。
class Foo(object):
def __init__(self, func):
self._func = func
def __call__(self):
print (‘class decorator runing’)
self._func()
print (‘class decorator ending’)
@Foo
def bar():
print (‘bar’)
bar()
functools.wraps
使用裝飾器極大地復用了代碼,但是他有一個缺點就是原函數的元信息不見了,比如函數的docstring、__name__、參數列表,先看例子:
裝飾器
def logged(func):
def with_logging(*args, **kwargs):
print func.__name__ + ” was called”
return func(*args, **kwargs)
return with_logging
函數
@loggeddef f(x):
“””does some math”””
return x + x * x
該函數完成等價於:
def f(x):
“””does some math”””
return x + x * xf = logged(f)
不難發現,函數f被with_logging取代了,當然它的docstring,__name__就是變成了with_logging函數的信息了。
print f.__name__ # prints ‘with_logging’print f.__doc__ # prints None
這個問題就比較嚴重的,好在我們有functools.wraps,wraps本身也是一個裝飾器,它能把原函數的元信息拷貝到裝飾器函數中,這使得裝飾器函數也有和原函數一樣的元信息了。
from functools import wrapsdef logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ + ” was called”
return func(*args, **kwargs)
return with_logging@loggeddef f(x):
“””does some math”””
return x + x * xprint f.__name__ # prints ‘f’print f.__doc__ # prints ‘does some math’
內置裝飾器
@staticmathod、@classmethod、@property
裝飾器的順序
@a@b@cdef f ():
等效於
f = a(b(c(f)))
python中更優雅的記錄日誌
在以往我們使用日誌,更多的是使用 python 自帶的 logging 模塊,它可以設置錯誤等級、輸出方式等。
但使用方式相對比較複雜,想要更好的使用需要如 log4net 一樣單獨配置,這在 python 中感覺不是很優雅。
下面介紹一個 python 庫: loguru 。 guru 是印度語中大師的意思, loguru 直譯就是「日誌大師」。
如圖 logging 一樣, loguru 也有定義日誌等級。不同的日誌等級,輸出效果也不一樣(默認的等級由低到高是 DEBUG 、 INFO 、 WARNING 、 ERROR 、 CRITICAL ,也可以自己使用 level 函數定義)。
類似 logging 中的 logger.addHandler ,loguru統一使用 add 函數來管理格式、文件輸出、過濾等操作,它提供了許多參數來實現 logger.addHandler 中的配置更加簡單方便。
其中 sink 是最重要的參數,可以傳入不同的數據類型。傳入文件路徑、文件句柄、 sys.stderr 、甚至 logging 模塊的 Handler 如 FileHandler 、 StreamHandler 等,這樣就可以快速實現自定義的 Handler 配置。
通過給 remove 方法傳遞 add 方法返回的對象, 可以刪除 add 方法添加的 sink ,這裡的 remove 並不是刪除 test2.log 文件,而是停止向該文件輸出日誌,需要需要繼續記錄日誌則需要重新 add 日誌文件。
用 rotation 、 retention 、 compression 進行日誌窗口、更新、壓縮管理。
支持控制台輸出添加顏色, 除了基礎色, loguru 甚至允許16進制、RGB格式的顏色值和加粗、下劃線等樣式。
使用裝飾器 @logger.catch 可以和 logging 一樣使用 logger.exception 函數來記錄異常信息。
使用 exception 方法輸出的異常信息包含堆棧信息和當前變量的值,方便問題定位。
使用 serialize 可以將日誌轉換為 JSON 格式, enqueue 可以保證多線程、多進程安全。
修改時間格式。
Loguru:Python 日誌終極解決方案
日誌的作用非常重要,日誌可以記錄用戶的操作、程序的異常,還可以為數據分析提供依據,日誌的存在意義就是為了能夠在程序在運行過程中記錄錯誤,方便維護和調試,能夠快速定位出錯的地方,減少維護成本。每個程序員都應該知道,不是為了記錄日誌而記錄日誌,日誌也不是隨意記的。要實現能夠只通過日誌文件還原整個程序執行的過程,達到能透明地看到程序里執行情況,每個線程、每個過程到底執行到哪的目的。日誌就像飛機的黑匣子一樣,應當能夠復原異常的整個現場乃至細節!
最常見的是把輸出函數 print() 當作日誌記錄的方式,直接打印各種提示信息,常見於個人練習項目里,通常是懶得單獨配置日誌,而且項目太小不需要日誌信息,不需要上線,不需要持續運行,完整的項目不推薦直接打印日誌信息,現實中也幾乎沒有人這麼做。
我們可以在不少小項目裏面看到作者自己寫了一個日誌模板,通常利用 print() 或者 sys.stdout 稍微封裝一下即可實現簡單的日誌輸出,這裡的 sys.stdout 是 Python 中的標準輸出流, print() 函數是對 sys.stdout 的高級封裝,當我們在 Python 中打印對象調用 print(obj) 時候,事實上是調用了 sys.stdout.write(obj+’\n’) , print() 將內容打印到了控制台,然後追加了一個換行符 \n 。
自寫日誌模板適合比較小的項目,可以按照自己的喜好編寫模板,不需要太多複雜配置,方便快捷,但是這種記錄日誌的方式並不是很規範,有可能你自己覺得閱讀體驗不錯,但是別人在接觸你的項目的時候往往需要花費一定的時間去學習日誌的邏輯、格式、輸出方式等,比較大的項目同樣不推薦這種方法。
一個簡單的自寫日誌模板舉例:
日誌模板 log.py:
調用日誌模塊:
日誌輸出:
在一個完整的項目中,大多數人都會引入專門的日誌記錄庫,而 Python 自帶的標準庫 logging 就是專門為日誌記錄而生的,logging 模塊定義的函數和類為應用程序和庫的開發實現了一個靈活的事件日誌系統。由標準庫模塊提供日誌記錄 API 的關鍵好處是所有 Python 模塊都可以使用這個日誌記錄功能。所以,你的應用日誌可以將你自己的日誌信息與來自第三方模塊的信息整合起來。
logging 模塊雖然強大,但是其配置也是比較繁瑣的,在大型項目中通常需要單獨初始化日誌、配置日誌格式等等,K哥在日常使用中通常都會對 logging 做如下的封裝寫法,使日誌可以按天保存,保留15天的日誌,可以配置是否輸出到控制台和文件,如下所示:
輸出日誌:
它在控制台中是這樣的:
當然,如果你不需要很複雜的功能,希望簡潔一點,僅僅需要在控制台輸出一下日誌的話,也可以只進行簡單的配置:
對於 logging 模塊,即便是簡單的使用,也需要自己定義格式,這裡介紹一個更加優雅、高效、簡潔的第三方模塊:loguru,官方的介紹是:Loguru is a library which aims to bring enjoyable logging in Python. Loguru 旨在為 Python 帶來愉快的日誌記錄。這裡引用官方的一個 GIF 來快速演示其功能:
Loguru 僅支持 Python 3.5 及以上的版本,使用 pip 安裝即可:
Loguru 的主要概念是只有一個:logger
控制台輸出:
可以看到不需要手動設置,Loguru 會提前配置一些基礎信息,自動輸出時間、日誌級別、模塊名、行號等信息,而且根據等級的不同,還自動設置了不同的顏色,方便觀察,真正做到了開箱即用!
如果想自定義日誌級別,自定義日誌格式,保存日誌到文件該怎麼辦?與 logging 模塊不同,不需要 Handler,不需要 Formatter,只需要一個 add() 函數就可以了,例如我們想把日誌儲存到文件:
我們不需要像 logging 模塊一樣再聲明一個 FileHandler 了,就一行 add() 語句搞定,運行之後會發現目錄下 test.log 裏面同樣出現了剛剛控制台輸出的 debug 信息。
與 add() 語句相反, remove() 語句可以刪除我們添加的配置:
此時控制台會輸出兩條 debug 信息:
而 test.log 日誌文件裏面只有一條 debug 信息,原因就在於我們在第二條 debug 語句之前使用了 remove() 語句。
Loguru 對輸出到文件的配置有非常強大的支持,比如支持輸出到多個文件,分級別分別輸出,過大創建新文件,過久自動刪除等等。 下面我們來詳細看一下 add() 語句的詳細參數:
基本語法:
基本參數釋義:
當且僅當 sink 是協程函數時,以下參數適用:
當且僅當 sink 是文件路徑時,以下參數適用:
這麼多參數可以見識到 add() 函數的強大之處,僅僅一個函數就能實現 logging 模塊的諸多功能,接下來介紹幾個比較常用的方法。
add() 函數的 rotation 參數,可以實現按照固定時間創建新的日誌文件,比如設置每天 0 點新創建一個 log 文件:
設置超過 500 MB 新創建一個 log 文件:
設置每隔一個周新創建一個 log 文件:
add() 函數的 retention 參數,可以設置日誌的最長保留時間,比如設置日誌文件最長保留 15 天:
設置日誌文件最多保留 10 個:
也可以是一個 datetime.timedelta 對象,比如設置日誌文件最多保留 5 個小時:
add() 函數的 compression 參數,可以配置日誌文件的壓縮格式,這樣可以更加節省存儲空間,比如設置使用 zip 文件格式保存:
其格式支持: gz 、 bz2 、 xz 、 lzma 、 tar 、 tar.gz 、 tar.bz2 、 tar.xz
Loguru 在輸出 log 的時候還提供了非常友好的字符串格式化功能,相當於 str.format() :
輸出:
在 Loguru 里可以直接使用它提供的裝飾器就可以直接進行異常捕獲,而且得到的日誌是無比詳細的:
日誌輸出:
在控制台的輸出是這樣的:
相比 Logging,Loguru 無論是在配置方面、日誌輸出樣式還是異常追蹤,都遠優於 Logging,使用 Loguru 無疑能提升開發人員效率。本文僅介紹了一些常用的方法,想要詳細了解可參考 Loguru 官方文檔 或關注 Loguru GitHub 。
python-復盤-裝飾器應用場景大總結
裝飾器能有助於檢查某個人是否被授權去使用一個web應用的端點(endpoint)。它們被大量使用於Flask和Django web框架中。這裡是一個例子來使用基於裝飾器的授權:
日誌是裝飾器運用的另一個亮點。這是個例子:
我敢肯定你已經在思考裝飾器的一個其他聰明用法了。
帶參數的裝飾器是典型的閉包函數 (略,參考我之前文章)
我們回到日誌的例子,並創建一個包裹函數,能讓我們指定一個用於輸出的日誌文件。
現在我們有了能用於正式環境的 logit 裝飾器,但當我們的應用的某些部分還比較脆弱時,異常也許是需要更緊急關注的事情。比方說有時你只想打日誌到一個文件。而有時你想把引起你注意的問題發送到一個email,同時也保留日誌,留個記錄。這是一個使用繼承的場景,但目前為止我們只看到過用來構建裝飾器的函數。
幸運的是,類也可以用來構建裝飾器。那我們現在以一個類而不是一個函數的方式,來重新構建logit。
具體再參考我 之前文章 ,廖神講解的更清晰
Python筆記:Python裝飾器
裝飾器是通過裝飾器函數修改原函數的一些功能而不需要修改原函數,在很多場景可以用到它,比如① 執行某個測試用例之前,判斷是否需要登錄或者執行某些特定操作;② 統計某個函數的執行時間;③ 判斷輸入合法性等。合理使用裝飾器可以極大地提高程序的可讀性以及運行效率。本文將介紹Python裝飾器的使用方法。
python裝飾器可以定義如下:
輸出:
python解釋器將test_decorator函數作為參數傳遞給my_decorator函數,並指向了內部函數 wrapper(),內部函數 wrapper() 又會調用原函數 test_decorator(),所以decorator()的執行會先打印’this is wrapper’,然後打印’hello world’, test_decorator()執行完成後,打印 ‘bye’ ,*args和**kwargs,表示接受任意數量和類型的參數。
裝飾器 my_decorator() 把真正需要執行的函數 test_decorator() 包裹在其中,並且改變了它的行為,但是原函數 test_decorator() 不變。
一般使用如下形式使用裝飾器:
@my_decorator就相當於 decorator = my_decorator(test_decorator) 語句。
內置裝飾器@functools.wrap可用於保留原函數的元信息(將原函數的元信息,拷貝到對應的裝飾器函數里)。先來看看沒有使用functools的情況:
輸出:
從上面的輸出可以看出test_decorator() 函數被裝飾以後元信息被wrapper() 函數取代了,可以使用@functools.wrap裝飾器保留原函數的元信息:
輸出:
裝飾器可以接受自定義參數。比如定義一個參數來設置裝飾器內部函數的執行次數:
輸出:
Python 支持多個裝飾器嵌套:
裝飾的過程:
順序從裡到外:
test_decorator(‘hello world’) 執行順序和裝飾的過程相反。
輸出:
類也可以作為裝飾器,類裝飾器主要依賴__call__()方法,是python中所有能被調用的對象具有的內置方法(python魔術方法),每當調用一個類的實例時,__call__()就會被執行一次。
下面的類裝飾器實現統計函數執行次數:
輸出:
下面介紹兩種裝飾器使用場景
統計函數執行所花費的時間
輸出:
在使用某些web服務時,需要先判斷用戶是否登錄,如果沒有登錄就跳轉到登錄頁面或者提示用戶登錄:
–THE END–
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/247584.html