Python裝飾器

  • Python 中的函數(shù)和 Java怔揩、C++不太一樣角撞,Python 中的函數(shù)可以像普通變量一樣當(dāng)做參數(shù)傳遞給另外一個函數(shù)
  • 本質(zhì)上憨奸,Python語言的decorator就是一個返回函數(shù)的高階函數(shù)
  • Python與Java裝飾器都是讓其他函數(shù)或類在不需要做任何代碼修改的前提下增加額外功能 。但python裝飾器直接實現(xiàn)了AOP剑逃,作用是Java中的注解凝化;Java中AOP要用Spring實現(xiàn)

背景

問題提出

  1. 夏天的短袖到冬天沒法為我們防風(fēng)御寒
  2. 我們想到的一個辦法就是把短袖改造一下稍坯,讓它變得更厚更長,這樣一來搓劫,它到冬天還能為我們防風(fēng)御寒(功能增強(qiáng))
  3. 但問題是瞧哟,這個內(nèi)褲被我們改造成了加厚長袖后混巧,雖然能為我們在冬天防風(fēng)御寒,但本質(zhì)上它不再是一件真正的短袖(改變了函數(shù))
  4. 我們想不改變短袖的本質(zhì)勤揩,卻還在冬天為我們防風(fēng)御寒
  5. 于是聰明的人們發(fā)明外套咧党,在不改變短袖的前提下,直接把外套套在了短袖外面雄可。這樣短袖還是短袖凿傅,套上外套后也能為我們在冬天防風(fēng)御寒(不改變函數(shù)缠犀,功能增強(qiáng))

python的函數(shù)

函數(shù)可以像普通變量一樣當(dāng)做參數(shù)傳遞給另外一個函數(shù)

def test():
    print("this is what I want")

def receive(func):
    print("I receive a function")
    func()

receive(test)
輸出

python函數(shù)的強(qiáng)大

裝飾器在 Python 使用如此方便都要?dú)w因于 Python 的函數(shù)能像普通的對象一樣能

  1. 作為參數(shù)傳遞給其他函數(shù)数苫,可以被賦值給其他變量
  2. 可以作為返回值
  3. 可以被定義在另外一個函數(shù)內(nèi)

裝飾器

意義

用于有切面需求的場景辨液,比如:插入日志虐急、性能測試、事務(wù)處理滔迈、緩存止吁、權(quán)限校驗等場景
用裝飾器抽離出大量與函數(shù)功能本身無關(guān)的雷同代碼到裝飾器中并繼續(xù)重用

定義

  • 裝飾器本質(zhì)上是一個 Python 函數(shù)或類,它可以讓其他函數(shù)或類在不需要做任何代碼修改的前提下增加額外功能
  • 裝飾器的返回值也是一個函數(shù)/類對象

例子

業(yè)務(wù)函數(shù)

def test():
    print("this is what I want")

新需求

現(xiàn)在有一個新的需求燎悍,希望可以記錄下函數(shù)的執(zhí)行日志敬惦,于是在代碼中添加日志代碼(新的需求):

def test():
    print("call test()")  #新需求
    print("this is what I want")

如果函數(shù) test()、test2()……等一大組函數(shù)也有類似的需求谈山,需要在這些函數(shù)里重復(fù)寫新需求俄删,造成問題:

  1. 修改工作量大
  2. 造成大量雷同的代碼
  3. 不利于之后的業(yè)務(wù)擴(kuò)展

新定義業(yè)務(wù)無關(guān)的函數(shù)

  • 重新定義一個輔助函數(shù)如log(func)專門處理日志(業(yè)務(wù)無關(guān)的代碼,新需求)
  • 日志處理完之后再執(zhí)行真正的業(yè)務(wù)代碼test()
def log(func):
    print("call test()") 
    func()

def test():
    print("this is what I want")

log(test)

[圖片上傳失敗...(image-fd6eee-1529993035512)]

存在問題

  • 上面的修改實現(xiàn)了功能(不改變業(yè)務(wù)函數(shù)奏路,加入新功能)
  • 但是調(diào)用的時候不再是調(diào)用真正的業(yè)務(wù)函數(shù)test()畴椰,而是調(diào)用輔助函數(shù)log()

這破壞了原有的代碼結(jié)構(gòu), 每次調(diào)用都要把原來的業(yè)務(wù)函數(shù)test()作為參數(shù)傳遞給輔助函數(shù)log()

簡單裝飾器

想要調(diào)用業(yè)務(wù)函數(shù)而不是 把業(yè)務(wù)函數(shù)作為參數(shù)傳給輔助函數(shù)鸽粉,就要用到裝飾器

def log(func):
    def wrapper():
        print("call test()")
        return func()     # 執(zhí)行傳入的函數(shù)參數(shù)斜脂,如test()
    return wrapper     #返回函數(shù)對象 wrapper

def test():
    print("this is what I want")

test= log(test) 
# 因為裝飾器 use_logging(test) 返回的是函數(shù)對象 wrapper
# 這條語句相當(dāng)于  test = wrapper

test()         # 執(zhí)行test() 相當(dāng)于執(zhí)行 wrapper()

輔助函數(shù)log()就是一個裝飾器,它把執(zhí)行真正業(yè)務(wù)函數(shù) func 包裹在其中触机,看起來像 test()log()裝飾了一樣帚戳。

這么做可以實現(xiàn)面向切面編程AOP。

裝飾器進(jìn)階

一. @ 語法糖

  • @ 符號是裝飾器的語法糖儡首,它放在函數(shù)開始定義的地方
  • 這樣就可以省略最后一步再次賦值的操作
    本例子中@use_logging相當(dāng)于test= use_logging(test)
def log(func):
    def wrapper():
        print("call test()")
        return func()
    return wrapper

@log
def test():
    print("this is what I want")

test()
輸出
  • 如此一來片任,業(yè)務(wù)函數(shù)test()不需要做任何修改,只需在定義的地方加上裝飾器椒舵,調(diào)用的時候不用加任何修飾
  • 如果我們有其他的類似函數(shù)蚂踊,我們可以繼續(xù)調(diào)用裝飾器如@log來修飾函數(shù),而不用重復(fù)修改函數(shù)或者增加新的封裝

這樣笔宿,我們就提高了程序的可重復(fù)利用性犁钟,并增加了程序的可讀性棱诱。

二. 帶參數(shù)的業(yè)務(wù)函數(shù)

*args、**kwargs用法

*args**kwargs是python中的可變參數(shù)涝动。

def foo(*args, **kwargs):
    print 'args = ', args
    print 'kwargs = ', kwargs
    print '---------------------------------------'

if __name__ == '__main__':
    foo(1,2,3,4)
    foo(a=1,b=2,c=3)
    foo(1,2,3,4, a=1,b=2,c=3)
    foo('a', 1, None, a=1, b='2', c=3)

輸出結(jié)果:

輸出
  • *args表示任何多個無名參數(shù)迈勋,它是一個tuple
  • **kwargs表示關(guān)鍵字參數(shù),它是一個dict
  • 同時使用*args**kwargs時醋粟,必須*args參數(shù)列要在**kwargs

有限個參數(shù)

當(dāng)業(yè)務(wù)函數(shù)test()需要參數(shù)靡菇,比如 test(a,b)

def test(a,b):
    print("a+b = %d" % (a+b))

我們可以在定義 wrapper()函數(shù)的時候指定參數(shù):

def log(func):
    def wrapper(a,b):
        print("call test(%d,%d)" %(a,b))
        return func(a,b)
    return wrapper

這樣業(yè)務(wù)函數(shù)test(name)定義的參數(shù)就可以定義在wrapper ()函數(shù)中米愿。

總的代碼:

# -*- coding: UTF-8 -*- 
def log(func):
    def wrapper(a,b):
        print("call test(%d厦凤,%d)" %(a,b))
        return func(a,b)
    return wrapper

@log
def test(a,b):
    print("sum = %d" % (a+b))

test(2,4)
輸出

可變參數(shù)

當(dāng)裝飾器不知道業(yè)務(wù)函數(shù)到底有多少個參數(shù)時,用*args 來代替育苟,

如此一來较鼓,不管業(yè)務(wù)函數(shù) test(…)定義了多少個參數(shù),都可以完整地傳遞到 func中去

def log(func):
    def wrapper(*args):
        print("call test()" )
        return func(*args)
    return wrapper

@log
def test(a,b,c):
    print("sum = %d" % (a+b+c))

test(2,4,5)

[圖片上傳失敗...(image-82a649-1529993035512)]

含關(guān)鍵字的可變參數(shù)

如果業(yè)務(wù)函數(shù) test(…)還定義了一些關(guān)鍵字參數(shù)违柏,用**kwargs來代替:

def test(a,b,c,way=None):
    print("sum = %d,way = %s" % ((a+b+c),way))

可以把 wrapper() 函數(shù)指定關(guān)鍵字函數(shù):

def log(func):
    def wrapper(*args, **kwargs):
        print("call test()" )
        return func(*args, **kwargs)
    return wrapper

總的:

def log(func):
    def wrapper(*args, **kwargs):
        print("call test()" )
        return func(*args, **kwargs)
    return wrapper

@log
def test(a,b,c,way=None):
    print("sum = %d,way = %s" % ((a+b+c),way))

test(2,4,5,"add")
輸出

三. 帶參數(shù)的裝飾器

裝飾器還有更大的靈活性博烂,例如帶參數(shù)的裝飾器,在上面的裝飾器調(diào)用中漱竖,該裝飾器接收唯一的參數(shù)就是業(yè)務(wù)函數(shù) test()
裝飾器的語法允許我們在調(diào)用時禽篱,提供其它參數(shù),比如@decorator(a)馍惹。這樣躺率,就為裝飾器的編寫和使用提供了更大的靈活性。比如讼积,我們可以在裝飾器中指定日志的等級肥照,因為不同業(yè)務(wù)函數(shù)可能需要的日志級別是不一樣的。

def log(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == "warn":
                print("%s waring" % func.__name__)
            elif level == "info":
                print("%s infomation get" % func.__name__)
            return func(*args, **kwargs)
        return wrapper
    return decorator


@log(level="warn")
def test():
    print("this is what I want")

@log(level="info")
def test2():
    print("this is also what I want")

test()
test2()
輸出

上面的 log() 是允許帶參數(shù)的裝飾器勤众。它實際上是對原有裝飾器的一個函數(shù)封裝舆绎,并返回一個裝飾器。我們可以將它理解為一個含有參數(shù)的閉包们颜。當(dāng)我們用@log(level="warn")調(diào)用的時候吕朵,Python 能夠發(fā)現(xiàn)這一層的封裝,并把參數(shù)傳遞到裝飾器的環(huán)境中窥突。

四. 類裝飾器

定義

  • 裝飾器不僅可以是函數(shù)努溃,還可以是類
  • 相比函數(shù)裝飾器,類裝飾器具有靈活度大阻问、高內(nèi)聚梧税、封裝性等優(yōu)點(diǎn)
  • __call__這樣前后都帶下劃線的方法在Python中被稱為內(nèi)置(魔法)方法。重載這些魔法方法一般會改變對象的內(nèi)部行為

用法

  1. 讓類的構(gòu)造函數(shù)__init__()接受一個函數(shù)
  2. 重載__call__()并 返回一個函數(shù)
  3. 使用@類形式將裝飾器附加到業(yè)務(wù)函數(shù)上
class DecorateDemo(object):
    def  __init__(self, func):
        self.__func = func
    def  __call__(self):
        print("before class decorator")
        self.__func()
        print("after class decorator")
        return self.__func

@DecorateDemo
def test():
    print("this is what I want")

test()
輸出

五. functools模塊

functools模塊提供了兩個裝飾器,主要用 functools.wraps

使用裝飾器極大地復(fù)用了代碼第队,但是他有一個缺點(diǎn)就是原函數(shù)的元信息不見了:

def log(func):
    def wrapper():
        print("call test()")
        return func()
    return wrapper

@log
def test():
    print("this is what I want")

test()
print test.__name__
改變了函數(shù)元信息
import functools

def log(func):
    @functools.wraps(func)
    def wrapper():
        print("call test()")
        return func()
    return wrapper

@log
def test():
    print("this is what I want")

test()
print test.__name__
@functools.wraps(func)

六. 多個裝飾器

裝飾器順序

一個函數(shù)還可以同時定義多個裝飾器哮塞,比如:

@a
@b
@c
def f ():
    pass

它的執(zhí)行順序是從里到外,最先調(diào)用最里層的裝飾器凳谦,最后調(diào)用最外層的裝飾器忆畅,它等效于

f = a(b(c(f)))

例子:

def log(func):
    def wrapper():
        print("call test()")
        result = func()
        print("end test()")
        return result 
    return wrapper

def hello(func):
    def wrapper():
        print("hello test()")
        result = func()
        print("goodbye test()")
        return result 
    return wrapper

@log
@hello
def test():
    print("this is what I want")

test()
多個裝飾器

參考文章:
Python裝飾器由淺入深
廖雪峰python裝飾器
理解 Python 裝飾器看這一篇就夠了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市尸执,隨后出現(xiàn)的幾起案子家凯,更是在濱河造成了極大的恐慌,老刑警劉巖如失,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绊诲,死亡現(xiàn)場離奇詭異,居然都是意外死亡岖常,警方通過查閱死者的電腦和手機(jī)驯镊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進(jìn)店門葫督,熙熙樓的掌柜王于貴愁眉苦臉地迎上來竭鞍,“玉大人,你說我怎么就攤上這事橄镜≠丝欤” “怎么了?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵洽胶,是天一觀的道長晒夹。 經(jīng)常有香客問我,道長姊氓,這世上最難降的妖魔是什么丐怯? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮翔横,結(jié)果婚禮上读跷,老公的妹妹穿的比我還像新娘。我一直安慰自己禾唁,他們只是感情好效览,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著荡短,像睡著了一般丐枉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上掘托,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天瘦锹,我揣著相機(jī)與錄音,去河邊找鬼。 笑死弯院,一個胖子當(dāng)著我的面吹牛噩峦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抽兆,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼识补,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了辫红?” 一聲冷哼從身側(cè)響起凭涂,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贴妻,沒想到半個月后切油,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡名惩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年澎胡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娩鹉。...
    茶點(diǎn)故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡攻谁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出弯予,到底是詐尸還是另有隱情戚宦,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布锈嫩,位于F島的核電站受楼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏呼寸。R本人自食惡果不足惜艳汽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望对雪。 院中可真熱鬧河狐,春花似錦、人聲如沸慌植。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蝶柿。三九已至丈钙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間交汤,已是汗流浹背雏赦。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工劫笙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人星岗。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓填大,卻偏偏與公主長得像,于是被迫代替她去往敵國和親俏橘。 傳聞我的和親對象是個殘疾皇子允华,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 呵呵!作為一名教python的老師寥掐,我發(fā)現(xiàn)學(xué)生們基本上一開始很難搞定python的裝飾器靴寂,也許因為裝飾器確實很難懂...
    TypingQuietly閱讀 19,535評論 26 186
  • 原文出處: dzone 譯文出處:Wu Cheng(@nullRef) 1. 函數(shù) 在python中,函數(shù)通過...
    DraculaWong閱讀 513評論 0 3
  • 轉(zhuǎn)載來自:http://blog.csdn.net/u013471155 在學(xué)習(xí)Python的過程中召耘,我相信有很多...
    JM68閱讀 575評論 3 9
  • 每個人都有的內(nèi)褲主要功能是用來遮羞百炬,但是到了冬天它沒法為我們防風(fēng)御寒,咋辦污它?我們想到的一個辦法就是把內(nèi)褲改造一下剖踊,...
    chen_000閱讀 1,360評論 0 3
  • 我的寫作水平不好德澈,所以只能簡單介紹: 94年湖南小哥一枚 性格簡單 愛好攝影攝像 工作婚禮攝像師 我喜歡帶著相機(jī)四...
    瘋子狗閱讀 1,053評論 32 11