Python裝飾器

Python裝飾器

  • 定義了一個函數(shù)
  • 想在運行時動態(tài)增加功能
  • 又不想改動函數(shù)本身代碼

通過高階函數(shù)返回新函數(shù)

def f1(x):
      return x*2
def new_fn(f):  # new_fn()函數(shù)就是裝飾器函數(shù)
        def fn(x):
              print 'call' + f._name_ + '()'
              return f(x)
        return fn

如何調(diào)用new_fn()裝飾器函數(shù)
方法1:

g1 = new_fn(f1)
print g1(5)

方法2:

f1 = new_fn(f1)
print f1(5)
# f1的原始定義函數(shù)被徹底隱藏了

Python內(nèi)置的@語法就是為了簡化裝飾器調(diào)用

@new_fn
def f1(x):
      return x*2

上述的代碼等價于下面的代碼:

def f1(x):
      return x*2

f1 = new_fn(f1)

裝飾器的作用:
可以極大地簡化代碼,避免每個函數(shù)編寫重復(fù)性代碼

Python中編寫無參數(shù)decorator

Python的 decorator 本質(zhì)上就是一個高階函數(shù),它接收一個函數(shù)作為參數(shù)肋杖,然后,返回一個新函數(shù)赃梧。

使用 decorator 用Python提供的 @ 語法玫坛,這樣可以避免手動編寫 f = decorate(f) 這樣的代碼。

考察一個@log的定義:

def log(f):
    def fn(x):
        print 'call ' + f.__name__ + '()...'
        return f(x)
    return fn

對于階乘函數(shù)签夭,@log工作得很好:

@log
def factorial(n):
    return reduce(lambda x,y: x*y, range(1, n+1))
print factorial(10)

結(jié)果:

call factorial()...
3628800

但是,對于參數(shù)不是一個的函數(shù)椎侠,調(diào)用將報錯:

@log
def add(x, y):
    return x + y
print add(1, 2)

結(jié)果:

Traceback (most recent call last):
  File "test.py", line 15, in <module>
    print add(1,2)
TypeError: fn() takes exactly 1 argument (2 given)

因為 add() 函數(shù)需要傳入兩個參數(shù)第租,但是 @log 寫死了只含一個參數(shù)的返回函數(shù)。

要讓 @log 自適應(yīng)任何參數(shù)定義的函數(shù)我纪,可以利用Python的 *args**kw慎宾,保證任意個數(shù)的參數(shù)總是能正常調(diào)用:

def log(f):
    def fn(*args, **kw):
        print 'call ' + f.__name__ + '()...'
        return f(*args, **kw)
    return fn

現(xiàn)在丐吓,對于任意函數(shù),@log 都能正常工作趟据。

python中編寫帶參數(shù)decorator

考察上一節(jié)的 @log 裝飾器:

def log(f):
    def fn(x):
        print 'call ' + f.__name__ + '()...'
        return f(x)
    return fn

發(fā)現(xiàn)對于被裝飾的函數(shù)券犁,log打印的語句是不能變的(除了函數(shù)名)。

如果有的函數(shù)非常重要汹碱,希望打印出'[INFO] call xxx()...'粘衬,有的函數(shù)不太重要,希望打印出'[DEBUG] call xxx()...'咳促,這時稚新,log函數(shù)本身就需要傳入'INFO'或'DEBUG'這樣的參數(shù),類似這樣:

@log('DEBUG')
def my_func():
    pass

把上面的定義翻譯成高階函數(shù)的調(diào)用跪腹,就是:

my_func = log('DEBUG')(my_func)

上面的語句看上去還是比較繞褂删,再展開一下:

log_decorator = log('DEBUG')
my_func = log_decorator(my_func)

上面的語句又相當于:

log_decorator = log('DEBUG')
@log_decorator
def my_func():
    pass

所以,帶參數(shù)的log函數(shù)首先返回一個decorator函數(shù)冲茸,再讓這個decorator函數(shù)接收my_func并返回新函數(shù):

def log(prefix):
    def log_decorator(f):
        def wrapper(*args, **kw):
            print '[%s] %s()...' % (prefix, f.__name__)
            return f(*args, **kw)
        return wrapper
    return log_decorator

@log('DEBUG')
def test():
    pass
print test()

執(zhí)行結(jié)果:

[DEBUG] test()...
None

對于這種3層嵌套的decorator定義笤妙,你可以先把它拆開:

# 標準decorator:
def log_decorator(f):
    def wrapper(*args, **kw):
        print '[%s] %s()...' % (prefix, f.__name__)
        return f(*args, **kw)
    return wrapper
return log_decorator

# 返回decorator:
def log(prefix):
    return log_decorator(f)

拆開以后會發(fā)現(xiàn),調(diào)用會失敗噪裕,因為在3層嵌套的decorator定義中蹲盘,最內(nèi)層的wrapper引用了最外層的參數(shù)prefix,所以膳音,把一個閉包拆成普通的函數(shù)調(diào)用會比較困難召衔。不支持閉包的編程語言要實現(xiàn)同樣的功能就需要更多的代碼。

Python中完善decorator

@decorator可以動態(tài)實現(xiàn)函數(shù)功能的增加祭陷,但是苍凛,經(jīng)過@decorator“改造”后的函數(shù),和原函數(shù)相比兵志,除了功能多一點外醇蝴,有沒有其它不同的地方?

在沒有decorator的情況下想罕,打印函數(shù)名:

def f1(x):
    pass
print f1.__name__

輸出:

f1

有decorator的情況下悠栓,再打印函數(shù)名:

def log(f):
    def wrapper(*args, **kw):
        print 'call...'
        return f(*args, **kw)
    return wrapper
@log
def f2(x):
    pass
print f2.__name__

輸出:

 wrapper

可見,由于decorator返回的新函數(shù)函數(shù)名已經(jīng)不是'f2'按价,而是@log內(nèi)部定義的'wrapper'惭适。這對于那些依賴函數(shù)名的代碼就會失效。decorator還改變了函數(shù)的__doc__等其它屬性楼镐。如果要讓調(diào)用者看不出一個函數(shù)經(jīng)過了@decorator的“改造”癞志,就需要把原函數(shù)的一些屬性復(fù)制到新函數(shù)中:

def log(f):
    def wrapper(*args, **kw):
        print 'call...'
        return f(*args, **kw)
    wrapper.__name__ = f.__name__
    wrapper.__doc__ = f.__doc__
    return wrapper

這樣寫decorator很不方便,因為我們也很難把原函數(shù)的所有必要屬性都一個一個復(fù)制到新函數(shù)上框产,所以Python內(nèi)置的functools可以用來自動化完成這個“復(fù)制”的任務(wù):

import functools
def log(f):
    @functools.wraps(f)
    def wrapper(*args, **kw):
        print 'call...'
        return f(*args, **kw)
    return wrapper

最后需要指出凄杯,由于我們把原函數(shù)簽名改成了(*args, **kw)错洁,因此,無法獲得原函數(shù)的原始參數(shù)信息戒突。即便我們采用固定參數(shù)來裝飾只有一個參數(shù)的函數(shù):

def log(f):
    @functools.wraps(f)
    def wrapper(x):
        print 'call...'
        return f(x)
    return wrapper

也可能改變原函數(shù)的參數(shù)名屯碴,因為新函數(shù)的參數(shù)名始終是'x',原函數(shù)定義的參數(shù)名不一定叫'x'妖谴。


2018.11.14更新:

裝飾器實際上是一個函數(shù)

有兩個特別之處:

  1. 參數(shù)是一個函數(shù)
  2. 返回值是一個函數(shù)

  1. 裝飾器使用的是通過@符號窿锉,放在函數(shù)上面
  2. 裝飾器中定義的函數(shù)酌摇,要使用*args,**kwargs兩對兄弟的組合膝舅,并且在這個函數(shù)中執(zhí)行原始函數(shù)的時候也要把*args,**kwargs傳過去。
  3. 需要使用functools.wraps在裝飾器中的函數(shù)上把傳進來的這個函數(shù)經(jīng)行一個包裹窑多,這樣就不會丟失原來的函數(shù)的name等屬性

# import functools

def login_required(f):
  """login_required"""
  def wrapper(*args,**kwargs):
    """wrapper"""
    return f(*args,**kwargs)
  return wrapper

@login_required
def t():
  """T"""
  print('tt')

print(t.__name__)
print(t.__doc__)
t()
#輸出:
wrapper
wrapper
tt
import functools

def login_required(f):
    """ login_required"""
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        """wrapper"""
        return f(*args, **kwargs)
    return wrapper

@ login_required  # t = login_required(t)
def t():
    """T"""
    print('tt')

print(t.__name__)
print(t.__doc__)
t()
輸出:
t
T
tt

python裝飾器后的函數(shù)名和文檔變化

裝飾器順序

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

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

它的執(zhí)行順序是從里到外,最先調(diào)用最里層的裝飾器埂息,最后調(diào)用最外層的裝飾器技潘,它等效于

f = a(b(c(f)))
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市千康,隨后出現(xiàn)的幾起案子享幽,更是在濱河造成了極大的恐慌,老刑警劉巖拾弃,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件值桩,死亡現(xiàn)場離奇詭異,居然都是意外死亡豪椿,警方通過查閱死者的電腦和手機奔坟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來搭盾,“玉大人咳秉,你說我怎么就攤上這事⊙煊纾” “怎么了澜建?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蝌以。 經(jīng)常有香客問我霎奢,道長,這世上最難降的妖魔是什么饼灿? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任幕侠,我火速辦了婚禮,結(jié)果婚禮上碍彭,老公的妹妹穿的比我還像新娘晤硕。我一直安慰自己悼潭,他們只是感情好,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布舞箍。 她就那樣靜靜地躺著舰褪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疏橄。 梳的紋絲不亂的頭發(fā)上占拍,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天,我揣著相機與錄音捎迫,去河邊找鬼晃酒。 笑死,一個胖子當著我的面吹牛窄绒,可吹牛的內(nèi)容都是我干的贝次。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼彰导,長吁一口氣:“原來是場噩夢啊……” “哼蛔翅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起位谋,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤山析,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后掏父,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體笋轨,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年损同,在試婚紗的時候發(fā)現(xiàn)自己被綠了翩腐。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡膏燃,死狀恐怖茂卦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情组哩,我是刑警寧澤等龙,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站伶贰,受9級特大地震影響蛛砰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜黍衙,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一泥畅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧琅翻,春花似錦位仁、人聲如沸柑贞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钧嘶。三九已至,卻和暖如春琳疏,著一層夾襖步出監(jiān)牢的瞬間有决,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工空盼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留书幕,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓我注,卻偏偏與公主長得像按咒,于是被迫代替她去往敵國和親迟隅。 傳聞我的和親對象是個殘疾皇子但骨,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

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

  • 呵呵!作為一名教python的老師智袭,我發(fā)現(xiàn)學生們基本上一開始很難搞定python的裝飾器奔缠,也許因為裝飾器確實很難懂...
    TypingQuietly閱讀 19,552評論 26 186
  • 原文出處: dzone 譯文出處:Wu Cheng(@nullRef) 1. 函數(shù) 在python中,函數(shù)通過...
    DraculaWong閱讀 521評論 0 3
  • 在學習Python的過程中吼野,我相信有很多人和我一樣校哎,對Python的裝飾器一直覺得很困惑,我也是困惑了好久瞳步,并通過...
    lijincheng閱讀 5,913評論 0 5
  • Python裝飾器的高級用法(翻譯) 原文地址https://www.codementor.io/python/t...
    城南道閱讀 4,813評論 1 22
  • 溫州江心游船是怎樣一種感覺闷哆?
    134749363728閱讀 124評論 1 1