本文的目錄如下:
裝飾器語法糖
入門用法:日志打印器
入門用法:時間計時器
進階用法:帶參數(shù)的函數(shù)裝飾器
高階用法:不帶參數(shù)的類裝飾器
高階用法:帶參數(shù)的類裝飾器
wrapper 裝飾器有啥用?
內置裝飾器:property
其他裝飾器:裝飾器實戰(zhàn)
Python學習交流群:83501,7344 獲取各類Python入門學習資料蚂维,群里每天都有大牛程序員進行直播上課唬渗!
- 裝飾器語法糖
如果你接觸 Python 有一段時間了的話堰酿,想必你對 @ 符號一定不陌生了良狈,沒錯 @ 符號就是裝飾器的語法糖徒像。
它放在一個可調用(callable)對象開始定義的地方黍特,它就像一頂帽子一樣戴在這個對象的頭上。和這個對象綁定在一起锯蛀。在我們調用這個對象的時候灭衷,第一件事并不是執(zhí)行這個對象,而是將這個對象做為參數(shù)傳入它頭頂上這頂帽子旁涤,這頂帽子我們稱之為裝飾函數(shù) 或 裝飾器翔曲。
你要問我裝飾器可以實現(xiàn)什么功能?我只能說你的腦洞有多大劈愚,裝飾器就有多強大瞳遍。
裝飾器的使用方法很固定:
先定義一個裝飾函數(shù)(帽子)
再定義你的業(yè)務函數(shù)、或者類(人)
最后把這頂帽子帶在這個人頭上
裝飾器的簡單的用法有很多菌羽,這里舉兩個常見的掠械。
日志打印器
時間計時器
- 入門用法:日志打印器
首先是日志打印器。 實現(xiàn)的功能:
在函數(shù)執(zhí)行前注祖,先打印一行日志告知一下主人猾蒂,我要執(zhí)行函數(shù)了。 在函數(shù)執(zhí)行完是晨,也不能拍拍屁股就走人了婚夫,咱可是有禮貌的代碼,再打印一行日志告知下主人署鸡,我執(zhí)行完啦。
這是裝飾函數(shù)
def logger(func):
def wrapper(*args, **kw):
print('我準備開始計算:{} 函數(shù)了:'.format(func.name))
# 真正執(zhí)行的是這行。
func(*args, **kw)
print('啊哈靴庆,我計算完啦时捌。給自己加個雞腿!炉抒!')
return wrapper
假如奢讨,我的業(yè)務函數(shù)是,計算兩個數(shù)之和焰薄。寫好后拿诸,直接給它帶上帽子。
@logger
def add(x, y):
print('{} + {} = {}'.format(x, y, x+y))
然后我們來計算一下塞茅。
add(200, 50)
快來看看輸出了什么亩码,神奇不?
我準備開始計算:add 函數(shù)了:
200 + 50 = 250
啊哈野瘦,我計算完啦描沟。給自己加個雞腿!
- 入門用法:時間計時器
再來看看 時間計時器 實現(xiàn)功能:
顧名思義鞭光,就是計算一個函數(shù)的執(zhí)行時長吏廉。
這是裝飾函數(shù)
def timer(func):
def wrapper(*args, *kw):
t1=time.time()
# 這是函數(shù)真正執(zhí)行的地方
func(args, **kw)
t2=time.time()
# 計算下時長
cost_time = t2-t1
print("花費時間:{}秒".format(cost_time))
return wrapper
假如,我們的函數(shù)是要睡眠10秒(冏~惰许,小明實在不知道要舉什么例子了)席覆。這樣也能更好的看出這個計算時長到底靠不靠譜。
import time
@timer
def want_sleep(sleep_time):
time.sleep(sleep_time)
want_sleep(10)
來看看汹买,輸出佩伤。真的是2秒耶。真歷害X远谩F杞洹!
花費時間:2.0073800086975098秒
- 進階用法:帶參數(shù)的函數(shù)裝飾器
通過上面簡單的入門结序,你大概已經感受到了裝飾的神奇魅力了障斋。
不過,裝飾器的用法遠不止如此徐鹤。我們今天就要把這個知識點講透垃环。
上面的例子,裝飾器是不能接收參數(shù)的返敬。其用法遂庄,只能適用于一些簡單的場景。不傳參的裝飾器劲赠,只能對被裝飾函數(shù)涛目,執(zhí)行固定邏輯秸谢。
如果你有經驗,你一定經常在項目中霹肝,看到有的裝飾器是帶有參數(shù)的估蹄。
裝飾器本身是一個函數(shù),既然做為一個函數(shù)都不能攜帶函數(shù)沫换,那這個函數(shù)的功能就很受限臭蚁。只能執(zhí)行固定的邏輯。這無疑是非常不合理的讯赏。而如果我們要用到兩個內容大體一致垮兑,只是某些地方不同的邏輯。不傳參的話漱挎,我們就要寫兩個裝飾器系枪。小明覺得這不能忍。
那么裝飾器如何實現(xiàn)傳參呢识樱,會比較復雜嗤无,需要兩層嵌套。
同樣怜庸,我們也來舉個例子当犯。
我們要在這兩個函數(shù)的執(zhí)行的時候,分別根據(jù)其國籍割疾,來說出一段打招呼的話嚎卫。
def american():
print("我來自中國。")
def chinese():
print("I am from America.")
在給他們倆戴上裝飾器的時候宏榕,就要跟裝飾器說拓诸,這個人是哪國人,然后裝飾器就會做出判斷麻昼,打出對應的招呼奠支。
戴上帽子后,是這樣的抚芦。
@say_hello("china")
def american():
print("我來自中國倍谜。")
@say_hello("america")
def chinese():
print("I am from America.")
萬事俱備,只差帽子了叉抡。來定義一下尔崔,這里需要兩層嵌套。
def say_hello(contry):
def wrapper(func):
def deco(*args, **kwargs):
if contry == "china":
print("你好!")
elif contry == "america":
print('hello.')
else:
return
# 真正執(zhí)行函數(shù)的地方
func(*args, **kwargs)
return deco
return wrapper
執(zhí)行一下
american()
print("------------")
chinese()
看看輸出結果褥民。
你好!
我來自中國季春。
hello.
I am from America
emmmm,這很NB消返。载弄。耘拇。
- 高階用法:不帶參數(shù)的類裝飾器
以上都是基于函數(shù)實現(xiàn)的裝飾器,在閱讀別人代碼時侦锯,還可以時常發(fā)現(xiàn)還有基于類實現(xiàn)的裝飾器驼鞭。
基于類裝飾器的實現(xiàn),必須實現(xiàn) call 和 init兩個內置函數(shù)尺碰。 init :接收被裝飾函數(shù) call :實現(xiàn)裝飾邏輯。
class logger(object):
def init(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("[INFO]: the function {func}() is running..."\
.format(func=self.func.__name__))
return self.func(*args, **kwargs)
@logger
def say(something):
print("say {}!".format(something))
say("hello")
執(zhí)行一下译隘,看看輸出
[INFO]: the function say() is running...
say hello!
- 高階用法:帶參數(shù)的類裝飾器
上面不帶參數(shù)的例子亲桥,你發(fā)現(xiàn)沒有,只能打印INFO級別的日志固耘,正常情況下题篷,我們還需要打印DEBUG WARNING等級別的日志。 這就需要給類裝飾器傳入?yún)?shù)厅目,給這個函數(shù)指定級別了番枚。
帶參數(shù)和不帶參數(shù)的類裝飾器有很大的不同。
init :不再接收被裝飾函數(shù)损敷,而是接收傳入?yún)?shù)葫笼。 call :接收被裝飾函數(shù),實現(xiàn)裝飾邏輯拗馒。
class logger(object):
def init(self, level='INFO'):
self.level = level
def __call__(self, func): # 接受函數(shù)
def wrapper(*args, **kwargs):
print("[{level}]: the function {func}() is running..."\
.format(level=self.level, func=func.__name__))
func(*args, **kwargs)
return wrapper #返回函數(shù)
@logger(level='WARNING')
def say(something):
print("say {}!".format(something))
say("hello")
我們指定WARNING級別路星,運行一下,來看看輸出诱桂。
[WARNING]: the function say() is running...
say hello!
- wrapper 裝飾器有啥用洋丐?
在 functools 標準庫中有提供一個 wrapper 裝飾器,你應該也經常見過挥等,那他有啥用呢友绝?
先來看一個例子
def wrapper(func):
def inner_function():
pass
return inner_function
@wrapper
def wrapped():
pass
print(wrapped.name)
inner_function
為什么會這樣子?不是應該返回 func 嗎肝劲?
這也不難理解迁客,因為上邊執(zhí)行func 和下邊 decorator(func) 是等價的,所以上面 func.name 是等價于下面decorator(func).name 的涡相,那當然名字是 inner_function
def wrapper(func):
def inner_function():
pass
return inner_function
def wrapped():
pass
print(wrapper(wrapped).name)
inner_function
那如何避免這種情況的產生哲泊?方法是使用 functools .wrapper 裝飾器,它的作用就是將 被修飾的函數(shù)(wrapped) 的一些屬性值賦值給 修飾器函數(shù)(wrapper) 催蝗,最終讓屬性的顯示更符合我們的直覺切威。
from functools import wraps
def wrapper(func):
@wraps(func)
def inner_function():
pass
return inner_function
@wrapper
def wrapped():
pass
print(wrapped.name)
wrapped
準確點說,wrapper 其實是一個偏函數(shù)對象(partial)丙号,源碼如下
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
可以看到wraps其實就是調用了一個函數(shù)update_wrapper先朦,知道原理后缰冤,我們改寫上面的代碼,在不使用 wraps的情況下喳魏,也可以讓 wrapped.name 打印出 wrapped棉浸,代碼如下:
from functools import update_wrapper
def wrapper(func):
def inner_function():
pass
update_wrapper(func, inner_function)
return inner_function
def wrapped():
pass
print(wrapped.name)
wrapped
- 內置裝飾器:property
以上,我們介紹的都是自定義的裝飾器刺彩。
其實Python語言本身也有一些裝飾器迷郑。比如property這個內建裝飾器,我們再熟悉不過了创倔。
它通常存在于類中嗡害,可以將一個函數(shù)定義成一個屬性,屬性的值就是該函數(shù)return的內容畦攘。
通常我們給實例綁定屬性是這樣的
class Student(object):
def init(self, name, age=None):
self.name = name
self.age = age
實例化
XiaoMing = Student("小明")
添加屬性
XiaoMing.age=25
查詢屬性
XiaoMing.age
刪除屬性
del XiaoMing.age
但是稍有經驗的開發(fā)人員霸妹,一下就可以看出,這樣直接把屬性暴露出去知押,雖然寫起來很簡單叹螟,但是并不能對屬性的值做合法性限制。為了實現(xiàn)這個功能台盯,我們可以這樣寫罢绽。
class Student(object):
def init(self, name):
self.name = name
self.name = None
def set_age(self, age):
if not isinstance(age, int):
raise ValueError('輸入不合法:年齡必須為數(shù)值!')
if not 0 < age < 100:
raise ValueError('輸入不合法:年齡范圍必須0-100')
self._age=age
def get_age(self):
return self._age
def del_age(self):
self._age = None
XiaoMing = Student("小明")
添加屬性
XiaoMing.set_age(25)
查詢屬性
XiaoMing.get_age()
刪除屬性
XiaoMing.del_age()
上面的代碼設計雖然可以變量的定義,但是可以發(fā)現(xiàn)不管是獲取還是賦值(通過函數(shù))都和我們平時見到的不一樣爷恳。 按照我們思維習慣應該是這樣的有缆。
賦值
XiaoMing.age = 25
獲取
XiaoMing.age
那么這樣的方式我們如何實現(xiàn)呢。請看下面的代碼温亲。
class Student(object):
def init(self, name):
self.name = name
self.name = None
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if not isinstance(value, int):
raise ValueError('輸入不合法:年齡必須為數(shù)值!')
if not 0 < value < 100:
raise ValueError('輸入不合法:年齡范圍必須0-100')
self._age=value
@age.deleter
def age(self):
del self._age
XiaoMing = Student("小明")
設置屬性
XiaoMing.age = 25
查詢屬性
XiaoMing.age
刪除屬性
del XiaoMing.age
用@property裝飾過的函數(shù)棚壁,會將一個函數(shù)定義成一個屬性,屬性的值就是該函數(shù)return的內容栈虚。同時袖外,會將這個函數(shù)變成另外一個裝飾器。就像后面我們使用的@age.setter和@age.deleter魂务。
@age.setter 使得我們可以使用XiaoMing.age = 25這樣的方式直接賦值曼验。 @age.deleter 使得我們可以使用del XiaoMing.age這樣的方式來刪除屬性。
- 其他裝飾器:裝飾器實戰(zhàn)
讀完并理解了上面的內容粘姜,你可以說是Python高手了鬓照。別懷疑,自信點孤紧,因為很多人都不知道裝飾器有這么多用法呢豺裆。
在小明看來,使用裝飾器号显,可以達到如下目的: - 使代碼可讀性更高臭猜,逼格更高躺酒; - 代碼結構更加清晰,代碼冗余度更低蔑歌;
剛好小明在最近也有一個場景羹应,可以用裝飾器很好的實現(xiàn),暫且放上來看看次屠。
這是一個實現(xiàn)控制函數(shù)運行超時的裝飾器园匹。如果超時,則會拋出超時異常劫灶。
有興趣的可以看看偎肃。
import signal
class TimeoutException(Exception):
def init(self, error='Timeout waiting for response from Cloud'):
Exception.init(self, error)
def timeout_limit(timeout_time):
def wraps(func):
def handler(signum, frame):
raise TimeoutException()
def deco(*args, **kwargs):
signal.signal(signal.SIGALRM, handler)
signal.alarm(timeout_time)
func(*args, **kwargs)
signal.alarm(0)
return deco
return wraps
以上就是個人對裝飾器用法的理解,整理不易浑此,若對你有所幫助,不防給個贊唄滞详。