一篇文章搞懂裝飾器所有用法(建議收藏)

本文的目錄如下:
裝飾器語法糖
入門用法:日志打印器
入門用法:時間計時器
進階用法:帶參數(shù)的函數(shù)裝飾器
高階用法:不帶參數(shù)的類裝飾器
高階用法:帶參數(shù)的類裝飾器
wrapper 裝飾器有啥用?
內置裝飾器:property
其他裝飾器:裝飾器實戰(zhàn)

Python學習交流群:83501,7344 獲取各類Python入門學習資料蚂维,群里每天都有大牛程序員進行直播上課唬渗!


  1. 裝飾器語法糖
    如果你接觸 Python 有一段時間了的話堰酿,想必你對 @ 符號一定不陌生了良狈,沒錯 @ 符號就是裝飾器的語法糖徒像。

它放在一個可調用(callable)對象開始定義的地方黍特,它就像一頂帽子一樣戴在這個對象的頭上。和這個對象綁定在一起锯蛀。在我們調用這個對象的時候灭衷,第一件事并不是執(zhí)行這個對象,而是將這個對象做為參數(shù)傳入它頭頂上這頂帽子旁涤,這頂帽子我們稱之為裝飾函數(shù) 或 裝飾器翔曲。

你要問我裝飾器可以實現(xiàn)什么功能?我只能說你的腦洞有多大劈愚,裝飾器就有多強大瞳遍。

裝飾器的使用方法很固定:

先定義一個裝飾函數(shù)(帽子)
再定義你的業(yè)務函數(shù)、或者類(人)
最后把這頂帽子帶在這個人頭上
裝飾器的簡單的用法有很多菌羽,這里舉兩個常見的掠械。

日志打印器
時間計時器

  1. 入門用法:日志打印器
    首先是日志打印器。 實現(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
啊哈野瘦,我計算完啦描沟。給自己加個雞腿!

  1. 入門用法:時間計時器
    再來看看 時間計時器 實現(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秒

  1. 進階用法:帶參數(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消返。载弄。耘拇。

  1. 高階用法:不帶參數(shù)的類裝飾器
    以上都是基于函數(shù)實現(xiàn)的裝飾器,在閱讀別人代碼時侦锯,還可以時常發(fā)現(xiàn)還有基于類實現(xiàn)的裝飾器驼鞭。

基于類裝飾器的實現(xiàn),必須實現(xiàn) callinit兩個內置函數(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!

  1. 高階用法:帶參數(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!

  1. 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

  1. 內置裝飾器: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這樣的方式來刪除屬性。

  1. 其他裝飾器:裝飾器實戰(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

以上就是個人對裝飾器用法的理解,整理不易浑此,若對你有所幫助,不防給個贊唄滞详。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末凛俱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子料饥,更是在濱河造成了極大的恐慌蒲犬,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岸啡,死亡現(xiàn)場離奇詭異原叮,居然都是意外死亡,警方通過查閱死者的電腦和手機巡蘸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門奋隶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人悦荒,你說我怎么就攤上這事唯欣。” “怎么了搬味?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵境氢,是天一觀的道長。 經常有香客問我碰纬,道長萍聊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任悦析,我火速辦了婚禮寿桨,結果婚禮上,老公的妹妹穿的比我還像新娘她按。我一直安慰自己牛隅,他們只是感情好炕柔,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著媒佣,像睡著了一般匕累。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上默伍,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天欢嘿,我揣著相機與錄音,去河邊找鬼也糊。 笑死炼蹦,一個胖子當著我的面吹牛,可吹牛的內容都是我干的狸剃。 我是一名探鬼主播掐隐,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼钞馁!你這毒婦竟也來了虑省?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤僧凰,失蹤者是張志新(化名)和其女友劉穎探颈,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體训措,經...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡伪节,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了绩鸣。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怀大。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖全闷,靈堂內的尸體忽然破棺而出叉寂,到底是詐尸還是另有隱情,我是刑警寧澤总珠,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布屏鳍,位于F島的核電站,受9級特大地震影響局服,放射性物質發(fā)生泄漏钓瞭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一淫奔、第九天 我趴在偏房一處隱蔽的房頂上張望山涡。 院中可真熱鬧,春花似錦、人聲如沸鸭丛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鳞溉。三九已至瘾带,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間熟菲,已是汗流浹背看政。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抄罕,地道東北人允蚣。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像呆贿,于是被迫代替她去往敵國和親嚷兔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內容

  • 閉包和裝飾器 1.8 閉包和裝飾器 學習目標 1. 能夠說出閉包的定義形式 2. 能夠說出裝飾器的實現(xiàn)形式 ...
    Cestine閱讀 534評論 0 0
  • 寫在前面的話 代碼中的# > 表示的是輸出結果 輸入 使用input()函數(shù) 用法 注意input函數(shù)輸出的均是字...
    FlyingLittlePG閱讀 2,743評論 0 8
  • 每個人都有的內褲主要功能是用來遮羞做入,但是到了冬天它沒法為我們防風御寒谴垫,咋辦?我們想到的一個辦法就是把內褲改造一下母蛛,...
    chen_000閱讀 1,360評論 0 3
  • 午后閑來無事,便想去奶奶家溜達溜達乳怎,在我家實在難找點往昔的年味彩郊,就去奶奶蹭年味了,哈哈~~ 走進奶奶家蚪缀,院子里屋里...
    hhy素言閱讀 630評論 1 5
  • 璧史蘊浮沉秫逝,瑜澤品淺深。 若非潔本質询枚,焉御外邪侵违帆? (新韻 網圖)
    珠江潮平閱讀 1,289評論 34 42