詳解裝飾器

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

1. 閉包裝飾器

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

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這個(gè)變量對(duì)于外層函數(shù)add_tag來說只是一個(gè)局部變量,但對(duì)與內(nèi)層函數(shù)add_content來說就是全局變量了,并且對(duì)封裝函數(shù)中任何一個(gè)變量,外部都無法訪問,這就形成了一個(gè)封閉的概念。

簡(jiǎn)單閉包裝飾器
做一個(gè)輸入檢查的問題,驗(yàn)證name=admin,password=123456。當(dāng)然如果對(duì)于多個(gè)函數(shù)接口都需要驗(yàn)證這樣的參數(shù),使用裝飾器是最好的選擇。

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

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

使用裝飾器以后,登陸函數(shù)中間不必做驗(yàn)證,只需要實(shí)現(xiàn)邏輯:

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

注意觀察外層接收函數(shù)對(duì)象,內(nèi)層接受參數(shù),那么裝飾就是這樣的

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

發(fā)現(xiàn)結(jié)果是一樣的,check_login(login)返回的是內(nèi)層函數(shù)對(duì)象,內(nèi)層函數(shù)剛好接收用戶名和密碼兩個(gè)參數(shù),也就是說@這個(gè)語(yǔ)法糖只是簡(jiǎn)化了展開式。

思考
仔細(xì)思考會(huì)發(fā)現(xiàn),這里有問題,這個(gè)登陸檢測(cè)裝飾器只接受兩個(gè)參數(shù),如何支持任意多個(gè)參數(shù)呢,這個(gè)問題還是留給讀者吧。

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

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.

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

import functools

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

    return _wrapper

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

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.

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

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

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

2. 類裝飾器

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

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

初始化返回的對(duì)象必須是可調(diào)用的,只需要改變類的__call__屬性,代碼是不是易懂多了。實(shí)現(xiàn)帶參數(shù)裝飾器,也就是添加日志的級(jí)別的裝飾器,稍稍麻煩一點(diǎn):

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.第三方包實(shí)現(xiàn)的裝飾器

wrapt這個(gè)包封裝了裝飾器,直接使用@wrapt.decorator裝飾在函數(shù)上即可,對(duì)于無參數(shù)裝飾器,實(shí)現(xiàn)很簡(jiǎn)單了,可讀性就不用多說了,畢竟是第三方的包。

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

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

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

小結(jié)

裝飾器非常重要,剛開始學(xué)習(xí)都是很困難的。當(dāng)然如果你要繞過困難,不學(xué)也可以實(shí)現(xiàn)相同的功能,只是你的代碼會(huì)很糟糕,可讀性差,代碼復(fù)用率低。Python就是簡(jiǎn)潔優(yōu)雅的,你非要寫出冗余代碼,真心浪費(fèi)了Guido的一片苦心,一個(gè)上午就寫了這么點(diǎn)文章,后期還會(huì)繼續(xù)完善。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、裝飾器原理 2、裝飾器語(yǔ)法 3、裝飾器執(zhí)行的時(shí)間 裝飾器在Python解釋器執(zhí)行的時(shí)候,就會(huì)進(jìn)行自動(dòng)裝飾,并不...
    HopCoder閱讀 1,220評(píng)論 0 0
  • 在學(xué)習(xí) Python 的時(shí)候,慶幸自己有 JavaScript 的基礎(chǔ),在學(xué)習(xí)過程中,發(fā)現(xiàn)許多相似的地方,如導(dǎo)包的...
    柏丘君閱讀 1,272評(píng)論 2 8
  • 要點(diǎn): 函數(shù)式編程:注意不是“函數(shù)編程”,多了一個(gè)“式” 模塊:如何使用模塊 面向?qū)ο缶幊蹋好嫦驅(qū)ο蟮母拍?、屬性?..
    victorsungo閱讀 1,694評(píng)論 0 6
  • 一.函數(shù)裝飾器 1.從Python內(nèi)層函數(shù)說起 首先我們來探討一下這篇文章所講的內(nèi)容Inner Functions...
    軟體動(dòng)物Ai閱讀 3,379評(píng)論 0 14
  • 2017年高考剛剛落幕,對(duì)浙江29萬余名考生來說,相比高考作文,語(yǔ)文試卷的一套閱讀理解風(fēng)頭更盛。今年浙江高考語(yǔ)文卷...
    凱美餐飲閱讀 696評(píng)論 0 0

友情鏈接更多精彩內(nèi)容