《Python進階》筆記3-裝飾器Decorator

裝飾器(Decorator)是Python的一個重要部分烧栋。簡單地說:它們是修改其它函數(shù)的功能的函數(shù)。

它們有助于讓我們的代碼更簡短珍特,也更Pythonic(Python范兒)魔吐。大多數(shù)初學(xué)者不知道在哪兒使用它們酬姆,所以我將要分享下,哪些區(qū)域里裝飾器可以讓你的代碼更簡潔骨宠。


1 一切皆對象

首先我們來理解下Python中的函數(shù)相满。

def hi(name="Jack"):
    return "hi " + name

print(hi())         # output: 'hi Jack'

我們甚至可以將一個函數(shù)賦值給一個變量,比如:

def hi(name="Jack"):
    return "hi " + name

greet = hi
print(greet())     # output: 'hi Jack'

我們這里沒有在使用小括號匿又,因為我們并不是在調(diào)用hi函數(shù)碌更,而是在將它放在greet變量里頭躲撰。我們嘗試運行下這個:

# 如果我們刪掉舊的hi函數(shù),看看會發(fā)生什么桦他!
def hi(name="Jack"):
    return "hi " + name

greet = hi
del hi
print(hi())          # output: NameError: name 'hi' is not defined
print(greet())       # output: 'hi Jack'

2 在函數(shù)中定義函數(shù)

剛才那些就是函數(shù)的基本知識了谆棱,更進一步垃瞧,在Python中我們可以在一個函
數(shù)中定義另一個函數(shù):

def hi(name="Jack"):
    print("now you are inside the hi() function")

    def greet():
        return "now you are in the greet() function"

    def welcome():
        return "now you are in the welcome() function"

    print(greet())
    print(welcome())
    print("now you are back in the hi() function")

hi()

# 輸出為:
now you are inside the hi() function
now you are in the greet() function
now you are in the welcome() function
now you are back in the hi() function

上面展示了無論何時你調(diào)用hi(), greet()和welcome()將會同時被調(diào)用。然后greet()和welcome()函數(shù)在hi()函數(shù)之外是不能訪問的脉幢,比如:

greet()
# 輸出為:
NameError Traceback (most recent call last)
<ipython-input-2-23aff5db816b> in <module>()
----> 1 greet()
NameError: name 'greet' is not defined

那現(xiàn)在我們知道了可以在函數(shù)中定義另外的函數(shù)嫌松。也就是說:我們可以創(chuàng)建嵌套的函數(shù)。現(xiàn)在你需要再多學(xué)一點液走,就是函數(shù)也能返回函數(shù)贾陷。


3 從函數(shù)中返回函數(shù)

其實并不需要在一個函數(shù)里去執(zhí)行另一個函數(shù),我們也可以將其作為輸出返回出來:

def hi(name="Jack"):
    def greet():
        return "now you are in the greet() function"

    def welcome():
        return "now you are in the welcome() function"

    if name == "Jack":
        return greet
    else:
        return welcome

a = hi()
print(a)     # output: <function greet at 0x0000000005894048>

print(a())   # output: now you are in the greet() function

上面清晰地展示了a現(xiàn)在指向到hi()函數(shù)中的greet()函數(shù)巷懈。

再次看看這個代碼砸喻。在if/else語句中我們返回greet和welcome蒋譬,而不是greet()和welcome()。為什么那樣癣漆?這是因為當你把一對小括號放在后面剂买,這個函數(shù)就會執(zhí)行瞬哼;然而如果你不放括號在它后面,那它可以被到處傳遞较性,并且可以賦值給別的變量而不去執(zhí)行它结胀。

你明白了嗎?讓我再稍微多解釋點細節(jié)攀操。

當我們寫下a = hi()秸抚,hi()會被執(zhí)行,而由于name參數(shù)默認是Jack颠放,所以函數(shù)greet被返回了慈迈。如果我們把語句改為a = hi(name = "ali")省有,那么welcome函數(shù)將被返回。此時打印出hi()()伸头,這會輸出now you are in the welcome() function舷蟀。


4 將函數(shù)作為參數(shù)傳給另一個函數(shù)

def hi():
    return "hi Jack!"

def doSomethingBeforeHi(func):
    print("I'm doing some exciting work before executing hi()")
    print(func())

doSomethingBeforeHi(hi)
# 輸出為:
I'm doing some exciting work before executing hi()
hi Jack!

現(xiàn)在你已經(jīng)具備所有必需知識野宜,來進一步學(xué)習(xí)裝飾器真正是什么了。裝飾器讓你在一個函數(shù)的前后去執(zhí)行代碼河胎。


5 你的第一個裝飾器

在上一個例子里虎敦,其實我們已經(jīng)創(chuàng)建了一個裝飾器其徙!現(xiàn)在我們修改下上一個裝飾器,并編寫一個稍微更有用點的程序:

def a_new_decorator(a_func):

    def wrapTheFunction():
        print("I'm doing some exciting work before executing a_func()")
        a_func()
        print("I'm doing some exciting work after executing a_func()")
    return wrapTheFunction

def a_function_requiring_decoration():
    print("I'm the function which needs some decoration to remove my foul smell")

a_function_requiring_decoration()
# 輸出為:
I'm the function which needs some decoration to remove my foul smell

a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
a_function_requiring_decoration()
# 輸出為:
I'm doing some exciting work before executing a_func()
I'm the function which needs some decoration to remove my foul smell
I'm doing some exciting work after executing a_func()

你看明白了嗎访锻?我們剛剛應(yīng)用了之前學(xué)習(xí)到的原理朗若。這正是python中裝飾器做的事情昌罩!它們封裝一個函數(shù),并且用這樣或那樣的方式來修改它的行為∏沧埽現(xiàn)在你也許疑惑,我們在代碼里并沒有使用@符號容达?那只是一個簡短的方式來生成一個被裝飾的函數(shù)垂券。

這里是我們?nèi)绾问褂聾來運行之前的代碼:

@a_new_decorator
def a_function_requiring_decoration():
    print("I'm the function which needs some decoration to remove my foul smell")

a_function_requiring_decoration()
# 輸出為:
I'm doing some exciting work before executing a_func()
I'm the function which needs some decoration to remove my foul smell
I'm doing some exciting work after executing a_func()

# 實際上菇爪,@a_new_decorator是以下用法的簡寫:
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)

希望你現(xiàn)在對Python裝飾器的工作原理有一個基本的理解。如果我們運行如下代碼會存在一個問題:

print(a_function_requiring_decoration.__name__)
# output: wrapTheFunction

這并不是我們想要的熙揍!Ouput輸出應(yīng)該a_function_requiring_decoration届囚。這里的函數(shù)被warpTheFunction替代了是尖。它重寫了我們函數(shù)的名字和注釋文檔(docstring)。

幸運的是Python提供給我們一個簡單的函數(shù)來解決這個問題昔字,那就是functools.wraps首繁。我們修改上一個例子來使用functools.wraps:

from functools import wraps

def a_new_decorator(a_func):
    @wraps(a_func)
    def wrapTheFunction():
        print("I'm doing some excting work before executing a_func()")
        a_func()
        print("I'm doing some exciting work after executing a_func()")
    return wrapTheFunction

@a_new_decorator
def a_function_requiring_decoration():
    print("I am the function which needs some decoration to remove my foul smell")

print(a_function_requiring_decoration.__name__)
# output: a_function_requiring_decoration

現(xiàn)在好多了弦疮。我們接下來學(xué)習(xí)裝飾器的一些常用場景胁塞。

以下是藍本規(guī)范:

from functools import wraps
def decorator_name(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if not can_run:
            return "Function will not run"
        return f(*args, **kwargs)
    return decorated

@decorator_name
def func():
    return("Function is running")

can_run = True
print(func())
# output: Function is running

can_run = False
print(func())
# output: Function will not run

注意:@wraps接受一個函數(shù)來進行裝飾,并加入了復(fù)制函數(shù)名稱编检、注釋文檔扰才、參數(shù)列表等等的功能衩匣。這可以讓我們在裝飾器里面訪問在裝飾之前的函數(shù)的屬性粥航。


6 使用場景

現(xiàn)在我們來看一下裝飾器在哪些地方特別耀眼生百,以及使用它可以讓一些事情管理起來變得更簡單蚀浆。


6.1 授權(quán)Authorization

裝飾器能有助于檢查某個人是否被授權(quán)去使用一個web應(yīng)用的端點(endpoint)。它們被大量使用于Flask和Django web框架中杠输。這里是一個例子來使用基于裝飾器的授權(quán):

from functools import wraps

def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            authenticate()
        return f(*args, **kwargs)
    return decorated

6.2 日志Logging

日志是裝飾器運用的另一個亮點。這是個例子:

from functools import wraps

def logit(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging

@logit
def addition_func(x):
   return x + x

result = addition_func(4)
# output: addition_func was called

我敢肯定你已經(jīng)在思考裝飾器的一個其他聰明用法了据忘。


7 帶參數(shù)的裝飾器

來想想這個問題搞糕,難道@wraps不也是個裝飾器嗎?但是汉规,它接收一個參數(shù)驹吮,就像任何普通的函數(shù)能做的那樣碟狞。那么,為什么我們不也那樣做呢频祝?

這是因為脆淹,當你使用@my_decorator語法時盖溺,你是在應(yīng)用一個以單個函數(shù)作為參數(shù)的一個包裹函數(shù)。記住兼蜈,Python里每個東西都是一個對象,而且包括函數(shù)歼郭!記住了這些辐棒,我們可以編寫一下能返回一個包裹函數(shù)的函數(shù)。


7.1 在函數(shù)中嵌入裝飾器

我們回到日志的例子泰涂,并創(chuàng)建一個包裹函數(shù)逼蒙,能讓我們指定一個用于輸出的日志文件寄疏。

from functools import wraps

def logit(logfile='out.log'):
    def logging_decorator(func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            with open(logfile, 'a') as opened_file:
                opened_file.write(log_string + '\n')
        return wrapped_function
    return logging_decorator

@logit()
def myfunc1():
    pass

myfunc1()
# output: myfunc1 was called
# 打開代碼所在的工作目錄陕截,會發(fā)現(xiàn)新建了一個名為out.log,此log的內(nèi)容為:myfunc1 was called

@logit(logfile='func2.log')
def myfunc2():
    pass

myfunc2()
# output: myfunc2 was called
# 打開代碼所在的工作目錄社搅,會發(fā)現(xiàn)新建了一個名為func2.log乳规,此log的內(nèi)容為:myfunc2 was called

7.2 裝飾器類

現(xiàn)在我們有了能用于正式環(huán)境的logit裝飾器暮的,但當我們的應(yīng)用的某些部分還比較脆弱時,異常也許是需要更緊急關(guān)注的事情源织。比方說有時你只想打日志到一個文件微猖。而有時你想把引起你注意的問題發(fā)送到一個email凛剥,同時也保留日志,留個記錄逻炊。這是一個使用繼承的場景,但目前為止我們只看到過用來構(gòu)建裝飾器的函數(shù)豹休。

幸運的是桨吊,類也可以用來構(gòu)建裝飾器视乐。那我們現(xiàn)在以一個類而不是一個函數(shù)的方式,來重新構(gòu)建logit留美。

class logit(object):
    def __init__(self, logfile='out.log'):
        self.logfile = logfile

    def __call__(self, func):
        log_string = func.__name__ + " was called"
        print(log_string)
        with open(self.logfile, 'a') as opened_file:
            opened_file.write(log_string + '\n')
        # Now, send a notification
        self.notify()

    def notify(self):
        pass

這個實現(xiàn)有一個附加優(yōu)勢独榴,在于比嵌套函數(shù)的方式更加整潔奕枝,而且包裹一個函數(shù)還是使用跟以前一樣的語法:

@logit()
def myfunc1():
    pass

現(xiàn)在瓶堕,我們給logit創(chuàng)建子類郎笆,來添加email的功能(雖然email這個話題不會在這里展開)。

class email_logit(logit):

    def __init__(self, email='admin@myproject.com', *args, **kwargs):
        self.email = email
        super(email_logit, self).__init__(*args, **kwargs)

    def notify(self):
        # 發(fā)送一封email到self.email激捏,這里就不做實現(xiàn)了
        pass

從現(xiàn)在起远舅,@email_logit將會和@logit產(chǎn)生同樣的效果痕钢,但是在打日志的基礎(chǔ)上任连,還會多發(fā)送一封郵件給管理員。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末繁涂,一起剝皮案震驚了整個濱河市二驰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌步势,老刑警劉巖坏瘩,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件倔矾,死亡現(xiàn)場離奇詭異柱锹,居然都是意外死亡,警方通過查閱死者的電腦和手機壤巷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門胧华,熙熙樓的掌柜王于貴愁眉苦臉地迎上來矩动,“玉大人释漆,你說我怎么就攤上這事∈咀耍” “怎么了逊笆?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵览露,是天一觀的道長。 經(jīng)常有香客問我命锄,道長脐恩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮崇猫,結(jié)果婚禮上需忿,老公的妹妹穿的比我還像新娘。我一直安慰自己屋厘,他們只是感情好涕烧,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著汗洒,像睡著了一般议纯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上溢谤,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天瞻凤,我揣著相機與錄音,去河邊找鬼溯香。 笑死,一個胖子當著我的面吹牛浓恶,可吹牛的內(nèi)容都是我干的玫坛。 我是一名探鬼主播包晰,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了膀息?” 一聲冷哼從身側(cè)響起秒际,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎树肃,沒想到半個月后蒸矛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年雏掠,在試婚紗的時候發(fā)現(xiàn)自己被綠了斩祭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡乡话,死狀恐怖摧玫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绑青,我是刑警寧澤诬像,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站闸婴,受9級特大地震影響坏挠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜掠拳,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一癞揉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧溺欧,春花似錦喊熟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至聂使,卻和暖如春壁拉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背柏靶。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工弃理, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人屎蜓。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓痘昌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親炬转。 傳聞我的和親對象是個殘疾皇子辆苔,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

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

  • 呵呵!作為一名教python的老師扼劈,我發(fā)現(xiàn)學(xué)生們基本上一開始很難搞定python的裝飾器驻啤,也許因為裝飾器確實很難懂...
    TypingQuietly閱讀 19,518評論 26 186
  • 一.函數(shù)裝飾器 1.從Python內(nèi)層函數(shù)說起 首先我們來探討一下這篇文章所講的內(nèi)容Inner Functions...
    軟體動物Ai閱讀 3,243評論 0 14
  • Python的裝飾器的英文名叫Decorator,要對一個已有的模塊做一些“修飾工作”荐吵,所謂修飾工作就是想給現(xiàn)有的...
    Spareribs閱讀 672評論 1 11
  • Python進階框架 希望大家喜歡骑冗,點贊哦首先感謝廖雪峰老師對于該課程的講解 一赊瞬、函數(shù)式編程 1.1 函數(shù)式編程簡...
    Gaolex閱讀 5,490評論 6 53
  • 今天爺爺去世了。 聽到消息時有一瞬間的思維停頓沐旨,爺爺?shù)囊羧菹嗝差D時涌現(xiàn)出來森逮,妹妹一邊掉淚一邊說上次帶孩子回來,爺爺...
    五月59閱讀 856評論 3 1