詳解裝飾器

裝飾器非常重要,不僅使代碼看起來更簡潔優雅,而且最大限度的復用了代碼。本文主要講解裝飾器的3種用法,分別是:
1、閉包裝飾器
2、類裝飾器
3、Python第三方模塊提供的方法
個人更推薦后面兩種,可讀性較好。這只是一個習慣而已,有人也覺得第一種更加簡潔。

1. 閉包裝飾器

閉包
閉包裝飾器含有兩個名詞分別是閉包和裝飾器,初學者可能不太清楚,在這里做一個簡單的說明。閉包在很多語言中都有這個概念,是指外層函數的局部變量是內層函數的全局變量(概念比較狹義,方便理解Python中的閉包)
我們來看這樣一個問題:

def add_tag(tag):
    def add_content(content):
        return "<%s> %s </%s>" % (tag, content, tag)

    return add_content

>>>a = add_tag("a")
>>>a(content="hello world")
<a> hello world </a>

tag這個變量對于外層函數add_tag來說只是一個局部變量,但對與內層函數add_content來說就是全局變量了,并且對封裝函數中任何一個變量,外部都無法訪問,這就形成了一個封閉的概念。

簡單閉包裝飾器
做一個輸入檢查的問題,驗證name=admin,password=123456。當然如果對于多個函數接口都需要驗證這樣的參數,使用裝飾器是最好的選擇。

def login(name, password):
    if name != "admin" or password != "123456":
        return "username or password error"

    print "login success."
    print "do something."

使用裝飾器以后,登陸函數中間不必做驗證,只需要實現邏輯:

def check_login(func):
    def _wrapper(name, password):
        if name != "admin" or password != "123456":
            print "login failed"
            return "username or password error"
        return func(name, password)

    return _wrapper


@check_login
def login(name, password):
    print "login success."
    print "do something."

>>>login("admin", "123456")
login success.
do something.
>>>login("admin", "156")
login failed

注意觀察外層接收函數對象,內層接受參數,那么裝飾就是這樣的

>>>check_login(login)("admin", "123456")
login success.
do something.
>>>check_login(login)("admin", "1256")
login failed

發現結果是一樣的,check_login(login)返回的是內層函數對象,內層函數剛好接收用戶名和密碼兩個參數,也就是說@這個語法糖只是簡化了展開式。

思考
仔細思考會發現,這里有問題,這個登陸檢測裝飾器只接受兩個參數,如何支持任意多個參數呢,這個問題還是留給讀者吧。

為了加深理解,再加一個日志裝飾器,輸出調用函數的名稱,兩個裝飾器一起裝飾login函數。(此處有坑,小心)

def log(func):
    def _wrapper(*args, **kwargs):
        print "call %s " % func.__name__
        return func(*args, **kwargs)

    return _wrapper


def check_login(func):
    def _wrapper(name, password):
        if name != "admin" or password != "123456":
            print "login failed"
            return "username or password error"
        return func(name, password)

    return _wrapper


@log
@check_login
def login(name, password):
    print "login success."
    print "do something."


>>>login("admin", "156")
call _wrapper 
login failed

>>>login("admin", "123456")
call _wrapper 
login success.
do something.

裝飾器的最外層函數接收的參數為函數名,這樣函數名就作為內部的全局變量,在任何地方都可以調用了,內層函數接受函數的參數,主要的邏輯放在內層函數里面,比如添加日志信息,或者做參數檢查等操作。最后就是按層級關系返回函數,但在這里還有一個問題,就是調用函數的名稱發生了變化,因為外層函數是由_wrapper返回的,因此最終得到的函數簽名就變成了_wrapper
只需要導入functools下的wraps,然后裝飾在內層函數上就能保證函數簽名的了。

import functools

def log(func):
    @functools.wraps(func)
    def _wrapper(*args, **kwargs):
        print "call %s " % func.__name__
        return func(*args, **kwargs)

    return _wrapper

給裝飾器添加參數
不僅需要輸出函數名的調用,還需要指定日志的級別,比如我們裝飾一個函數@log(info),這個函數就得輸出[info] call login。只需要在最外層改變接受參數,次內層接受函數名,內層接受函數的參數,也就是加了三層,看起來比較復雜了。

import functools

def log(level):
    def _decorator(func):
        @functools.wraps(func)
        def _wrapper(*args, **kwargs):
            print "[%s] call %s " % (level, func.__name__)
            return func(*args, **kwargs)

        return _wrapper

    return _decorator

@log("info")
def login(name, password):
    print "login success."
    print "do something."


>>>login("admin", "123456")
[info] call login 
login success.
do something.

對于三層的裝飾器,@語法糖相當于log("info")(login)("admin", "123456"),看著是不是有點蒙了,(我剛開始學習的時候也是抓狂的,所以推薦后面兩種),理解以后用哪種就看個人口味了。給出部分代碼如下:

def login(name, password):   
    print "login success."   
    print "do something."

>>>log("info")(login)("admin", "123456")
[info] call login 
login success.
do something.

2. 類裝飾器

最麻煩的閉包裝飾器解決了,類裝飾器寫起來容易,并且可讀性好多了,廢話不多說,直接實現上面日志例子吧。

class Log(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print "call %s" % self.func.__name__
        return self.func(*args, **kwargs)

@Log
def login(name, password):
    print name, password

>>> login("admin", "123456")
call login
admin 123456

初始化返回的對象必須是可調用的,只需要改變類的__call__屬性,代碼是不是易懂多了。實現帶參數裝飾器,也就是添加日志的級別的裝飾器,稍稍麻煩一點:

class Log(object):
    def __init__(self, level):
        self.level = level

    def __call__(self, func):
        def _wrapper(*args, **kwargs):
            print "[%s] call %s" % (self.level, func.__name__)
            return func(*args, **kwargs)

        return _wrapper

@Log("debug")
def login(name, password):
    print name, password

>>> login("admin", "123456")
[debug] call login
admin 123456

3.第三方包實現的裝飾器

wrapt這個包封裝了裝飾器,直接使用@wrapt.decorator裝飾在函數上即可,對于無參數裝飾器,實現很簡單了,可讀性就不用多說了,畢竟是第三方的包。

import wrapt

@wrapt.decorator
def log(func, instance, args, kwargs):
    print "call %s" % func.__name__
    return func(*args, **kwargs)

@log
def login(name, password):
    print name, password


>>>login("admin", "123456")
call login
admin 123456

有參數的情況,還需要在外面加一層函數,這層函數用來接收參數。

import wrapt

def log(level):
    @wrapt.decorator
    def wrapper(func, instance, args, kwargs):
        print "[%s]: call %s" % (level, func.__name__)
        return func(*args, **kwargs)

    return wrapper

@Log("debug")
def login(name, password):
    print name, password


>>>login("admin", "123456")
[debug] call login
admin 123456

小結

裝飾器非常重要,剛開始學習都是很困難的。當然如果你要繞過困難,不學也可以實現相同的功能,只是你的代碼會很糟糕,可讀性差,代碼復用率低。Python就是簡潔優雅的,你非要寫出冗余代碼,真心浪費了Guido的一片苦心,一個上午就寫了這么點文章,后期還會繼續完善。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內容

  • 1、裝飾器原理 2、裝飾器語法 3、裝飾器執行的時間 裝飾器在Python解釋器執行的時候,就會進行自動裝飾,并不...
    HopCoder閱讀 1,201評論 0 0
  • 在學習 Python 的時候,慶幸自己有 JavaScript 的基礎,在學習過程中,發現許多相似的地方,如導包的...
    柏丘君閱讀 1,216評論 2 8
  • 要點: 函數式編程:注意不是“函數編程”,多了一個“式” 模塊:如何使用模塊 面向對象編程:面向對象的概念、屬性、...
    victorsungo閱讀 1,548評論 0 6
  • 一.函數裝飾器 1.從Python內層函數說起 首先我們來探討一下這篇文章所講的內容Inner Functions...
    軟體動物Ai閱讀 3,267評論 0 14
  • 2017年高考剛剛落幕,對浙江29萬余名考生來說,相比高考作文,語文試卷的一套閱讀理解風頭更盛。今年浙江高考語文卷...
    凱美餐飲閱讀 623評論 0 0