談?wù)刾ython修飾器

photo-1519320669750-579a8d7f1f6a.jpg

前言

對(duì)python的修飾器的理解一直停留在"使用修飾器把函數(shù)注冊(cè)為事件的處理程序"的層次恨搓,也是一知半解;這樣拖著不是辦法灾前,索性今天好好整理一下關(guān)于python修飾器的概念及用法趴拧。

介紹

裝飾器是一個(gè)很著名的設(shè)計(jì)模式,經(jīng)常被用于有切面需求的場(chǎng)景懈费,較為經(jīng)典的有插入日志计露、性能測(cè)試、事務(wù)處理等憎乙。裝飾器是解決這類(lèi)問(wèn)題的絕佳設(shè)計(jì)票罐,有了裝飾器,我們就可以抽離出大量函數(shù)中與函數(shù)功能本身無(wú)關(guān)的雷同代碼并繼續(xù)重用泞边。概括的講该押,裝飾器的作用就是為已經(jīng)存在的對(duì)象添加額外的功能。

功能

我們首先從一個(gè)簡(jiǎn)單的例子說(shuō)起阵谚,這個(gè)例子是stackflow上的一個(gè)問(wèn)題蚕礼,如何通過(guò)使用如下的代碼實(shí)現(xiàn)輸出<b><i>Hello</i></b>:

@makebold  
@makeitalic  
def say():  
   return "Hello"

先看一下答案:

def makebold(fn):  
    def wrapped():  
        return "<b>" + fn() + "</b>"  
    return wrapped  
   
def makeitalic(fn):  
    def wrapped():  
        return "<i>" + fn() + "</i>"  
    return wrapped  
  
@makebold  
@makeitalic  
def hello():  
    return "hello world"  
   
print hello() ## 返回 <b><i>hello world</i></b> 

這里的@makebold@makeitalic似乎給Hello加上了一層包裝(or修飾),這就是修飾器最明顯的體現(xiàn)梢什。

從需求談起

初期奠蹬,我寫(xiě)了一個(gè)函數(shù)

def foo():  
    print 'in foo()'  
foo() 

為了檢查這個(gè)函數(shù)的復(fù)雜度(在網(wǎng)絡(luò)編程中程序的延時(shí)還是很重要的),需要測(cè)算運(yùn)算時(shí)間嗡午,增加了計(jì)算時(shí)間的功能有了下面的代碼:

import time  
def foo():  
    start = time.clock()  
    print 'in foo()'  
    end = time.clock()  
    print 'Time Elapsed:', end - start  
   
foo()  

這里只是寫(xiě)了一個(gè)函數(shù)囤躁,如果我想測(cè)量多個(gè)函數(shù)的延時(shí),由于必須知道start與end荔睹,所以必須寫(xiě)在程序的開(kāi)頭與結(jié)尾狸演,難道每一個(gè)程序都這樣復(fù)制粘貼么?固然可行应媚,但是严沥,我們可以通過(guò)設(shè)計(jì)模式中將功能與數(shù)據(jù)部分分離一樣,將這個(gè)測(cè)量時(shí)間的函數(shù)分離出去中姜,就像C++中我們可以將這個(gè)測(cè)量時(shí)間的函數(shù)變?yōu)橐粋€(gè)類(lèi)消玄,通過(guò)調(diào)用這個(gè)類(lèi),賦予不同的函數(shù)來(lái)測(cè)量不同的函數(shù)的運(yùn)行時(shí)長(zhǎng)丢胚。在python中翩瓜,由于函數(shù)實(shí)際上就是對(duì)象,所以可以利用類(lèi)似的方法實(shí)現(xiàn):

import time  
   
def foo():  
    print 'in foo()'  
   
def timeit(func):  
    start = time.clock()  
    func()  
    end =time.clock()  
    print 'Time Elapsed:', end - start  
   
timeit(foo)  

這里func()就可以指定函數(shù)了携龟,但是如果我不想填這個(gè)函數(shù)或者這個(gè)功能函數(shù)并不能修改成類(lèi)似的形式怎么辦兔跌?我們需要的是最大限度的少改動(dòng):

import time  
   
def foo():  
    print 'in foo()'  
   
# 定義一個(gè)計(jì)時(shí)器,傳入一個(gè)峡蟋,并返回另一個(gè)附加了計(jì)時(shí)功能的方法  
def timeit(func):  
       
    # 定義一個(gè)內(nèi)嵌的包裝函數(shù)坟桅,給傳入的函數(shù)加上計(jì)時(shí)功能的包裝  
    def wrapper():  
        start = time.clock()  
        func()  
        end =time.clock()  
        print 'Time Elapsed:', end - start  
       
    # 將包裝后的函數(shù)返回  
    return wrapper  
   
foo = timeit(foo)   #可以直接寫(xiě)成@timeit + foo定義华望,python的"語(yǔ)法糖"
foo()

在這個(gè)代碼中,timeit(foo)不是直接產(chǎn)生調(diào)用效果仅乓,而是返回一個(gè)與foo參數(shù)列表一致的函數(shù)赖舟,此時(shí)此foo非彼foo!因?yàn)榇藭r(shí)的foo具有了timeit的功效夸楣,簡(jiǎn)單來(lái)說(shuō)就是能夠讓你在裝飾前后執(zhí)行代碼而無(wú)須改變函數(shù)本身內(nèi)容宾抓,裝飾器是一個(gè)函數(shù),而其參數(shù)為另外一個(gè)函數(shù)豫喧。

一個(gè)有趣的"漢堡"讓你了解順序

順序在修飾器還是非常重要的石洗,利用一個(gè)代碼展示一下:

def bread(func) :  
    def wrapper() :  
        print "</'''       '''\>"  
        func()  
        print "<\______/>"  
    return wrapper  
   
def ingredients(func) :  
    def wrapper() :  
        print "#tomatoes#"  
        func()  
        print "~salad~"  
    return wrapper  
   
def sandwich(food="--ham--") :  
    print food  
   
sandwich()  
#輸出 : --ham--  
sandwich = bread(ingredients(sandwich))  
sandwich()  

#輸出:  

#</'''       '''\>  
# #tomatoes#  
# --ham--  
# ~salad~  
#<\______/>  

加上語(yǔ)法糖,代碼可以更簡(jiǎn)潔:

def bread(func) :  
    def wrapper() :  
        print "</'''       '''\>"  
        func()  
        print "<\______/>"  
    return wrapper  
   
def ingredients(func) :  
    def wrapper() :  
        print "#tomatoes#"  
        func()  
        print "~salad~"  
    return wrapper  

@bread  
@ingredients  
def sandwich(food="--ham--") :  
    print food  
   
sandwich()

拓展

內(nèi)置修飾器

內(nèi)置的裝飾器有三個(gè)紧显,分別是staticmethod讲衫、classmethod和property,作用分別是把類(lèi)中定義的實(shí)例方法變成靜態(tài)方法孵班、類(lèi)方法和類(lèi)屬性焦人。

對(duì)有參函數(shù)進(jìn)行修飾

一個(gè)參數(shù)

如果原函數(shù)有參數(shù),那閉包函數(shù)必須保持參數(shù)個(gè)數(shù)一致重父,并且將參數(shù)傳遞給原方法

def w1(fun):
    def wrapper(name):
        print("this is the wrapper head")
        fun(name)
        print("this is the wrapper end")
    return wrapper

@w1
def hello(name):
    print("hello"+name)

hello("world")

# 輸出:
# this is the wrapper head
# helloworld
# this is the wrapper end

多個(gè)參數(shù)測(cè)試:

def w2(fun):
    def wrapper(*args,**kwargs):
        print("this is the wrapper head")
        fun(*args,**kwargs)
        print("this is the wrapper end")
    return wrapper

@w2
def hello(name,name2):
    print("hello"+name+name2)

hello("world","!!!")

#輸出:
# this is the wrapper head
# helloworld!!!
# this is the wrapper end

有返回值的函數(shù)


def w3(fun):
    def wrapper():
        print("this is the wrapper head")
        temp=fun()
        print("this is the wrapper end")
        return temp   #要把值傳回去呀;ㄍ帧!
    return wrapper

@w3
def hello():
    print("hello")
    return "test"

result=hello()
print("After the wrapper,I accept %s" %result)

#輸出:
#this is the wrapper head
#hello
#this is the wrapper end
#After the wrapper,I accept test

有參數(shù)的修飾器

直接上代碼:

def func_args(pre='xiaoqiang'):
    def w_test_log(func):
        def inner():
            print('...記錄日志...visitor is %s' % pre)
            func()

        return inner

    return w_test_log


# 帶有參數(shù)的修飾器能夠起到在運(yùn)行時(shí)房午,有不同的功能

# 先執(zhí)行func_args('wangcai')矿辽,返回w_test_log函數(shù)的引用
# @w_test_log
# 使用@w_test_log對(duì)test_log進(jìn)行修飾
@func_args('wangcai')
def test_log():
    print('this is test log')


test_log()

#輸出:
#...記錄日志...visitor is wangcai
# this is test log

通用修飾器

對(duì)每個(gè)類(lèi)型都有一個(gè)修飾器形式,怎么記得下來(lái)郭厌?所以就有了這個(gè)"萬(wàn)能修飾器":

def w_test(func):
    def inner(*args, **kwargs):
        ret = func(*args, **kwargs)
        return ret

    return inner


@w_test
def test():
    print('test called')


@w_test
def test1():
    print('test1 called')
    return 'python'


@w_test
def test2(a):
    print('test2 called and value is %d ' % a)


test()
test1()
test2(9)

# 輸出:
#test called
#test1 called
#test2 called and value is 9 

類(lèi)修飾器

當(dāng)創(chuàng)建一個(gè)對(duì)象后袋倔,直接去執(zhí)行這個(gè)對(duì)象,那么是會(huì)拋出異常的折柠,因?yàn)樗皇莄allable宾娜,無(wú)法直接執(zhí)行,但進(jìn)行修改后扇售,就可以直接執(zhí)行調(diào)用:

class Test(object):
    def __call__(self, *args, **kwargs):
        print('call called')


t = Test()
print(t())
# 就可以直接執(zhí)行

直接對(duì)類(lèi)進(jìn)行修飾:

class Test(object):
    def __init__(self, func):
        print('test init')
        print('func name is %s ' % func.__name__)
        self.__func = func

    def __call__(self, *args, **kwargs):
        print('this is wrapper')
        self.__func()


@Test
def test():
    print('this is test func')


test()

#輸出:
# test init
# func name is test 
# this is wrapper
# this is test func

后記

先介紹到這里前塔,大致也對(duì)修飾器有了一定的理解。后面自己會(huì)結(jié)合自己的項(xiàng)目繼續(xù)深入學(xué)習(xí)承冰。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末华弓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子困乒,更是在濱河造成了極大的恐慌寂屏,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異迁霎,居然都是意外死亡吱抚,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)考廉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)频伤,“玉大人,你說(shuō)我怎么就攤上這事芝此。” “怎么了因痛?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵婚苹,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我鸵膏,道長(zhǎng)膊升,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任谭企,我火速辦了婚禮廓译,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘债查。我一直安慰自己非区,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布盹廷。 她就那樣靜靜地躺著征绸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪俄占。 梳的紋絲不亂的頭發(fā)上管怠,一...
    開(kāi)封第一講書(shū)人閱讀 49,950評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音缸榄,去河邊找鬼渤弛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛甚带,可吹牛的內(nèi)容都是我干的她肯。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼鹰贵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼辕宏!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起砾莱,我...
    開(kāi)封第一講書(shū)人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤瑞筐,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體聚假,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡块蚌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了膘格。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片峭范。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖瘪贱,靈堂內(nèi)的尸體忽然破棺而出纱控,到底是詐尸還是另有隱情,我是刑警寧澤菜秦,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布甜害,位于F島的核電站,受9級(jí)特大地震影響球昨,放射性物質(zhì)發(fā)生泄漏尔店。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一主慰、第九天 我趴在偏房一處隱蔽的房頂上張望嚣州。 院中可真熱鬧,春花似錦共螺、人聲如沸该肴。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)沙庐。三九已至,卻和暖如春佳吞,著一層夾襖步出監(jiān)牢的瞬間拱雏,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工底扳, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铸抑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓衷模,卻偏偏與公主長(zhǎng)得像鹊汛,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子阱冶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

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

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理刁憋,服務(wù)發(fā)現(xiàn),斷路器木蹬,智...
    卡卡羅2017閱讀 134,637評(píng)論 18 139
  • 前言 人生苦多至耻,快來(lái) Kotlin ,快速學(xué)習(xí)Kotlin! 什么是Kotlin尘颓? Kotlin 是種靜態(tài)類(lèi)型編程...
    任半生囂狂閱讀 26,173評(píng)論 9 118
  • 1.概念:裝飾器是一個(gè)很著名的設(shè)計(jì)模式走触,經(jīng)常被用于有切面需求的場(chǎng)景,較為經(jīng)典的應(yīng)用有插入日志疤苹、增加計(jì)時(shí)邏輯來(lái)檢測(cè)性...
    rtrhhthth閱讀 401評(píng)論 0 0
  • 馬上升六年級(jí)的我互广,在我的時(shí)光里都是滿(mǎn)滿(mǎn)的回憶,所以他就像一杯濃濃的咖啡特別的濃郁卧土,就像一杯人生的苦咖啡...
    張靖涵閱讀 228評(píng)論 0 0
  • 相思 作者:嶼上日光 遇見(jiàn)若是錯(cuò) 何苦又相識(shí) 而今苦相思 心已千千結(jié)
    嶼上日光閱讀 149評(píng)論 0 0