裝飾器

  • 裝飾器是程序開發(fā)中經(jīng)常會用到的一個(gè)功能匈子,用好了裝飾器,開發(fā)效率如虎添翼讳嘱,所以這也是Python面試中必問的問題幔嗦,但對于好多初次接觸這個(gè)知識的人來講,這個(gè)功能有點(diǎn)繞沥潭,自學(xué)時(shí)直接繞過去了邀泉,然后面試問到了就掛了,因?yàn)檠b飾器是程序開發(fā)的基礎(chǔ)知識钝鸽。

裝飾器(decorator)功能

  1. 引入日志
  2. 函數(shù)執(zhí)行時(shí)間統(tǒng)計(jì)
  3. 執(zhí)行函數(shù)前預(yù)備處理
  4. 執(zhí)行函數(shù)后清理功能
  5. 權(quán)限校驗(yàn)等場景
  6. 緩存

個(gè)人理解+示例

import time

"""
裝飾器理解:
前提:
定義一個(gè)函數(shù) def foo()
foo是函數(shù)的引用
foo()是執(zhí)行這個(gè)函數(shù)

定義一個(gè)閉包c(diǎn)alTime(func)呼渣,把函數(shù)的引用的傳到閉包中,保留該函數(shù)寞埠,并可以通過()調(diào)用函數(shù)
通過@calTime 使test = callTime(test)屁置,新的函數(shù)中有之前函數(shù)的引用(因?yàn)殚]包的特性),
并且 在沒有調(diào)用函數(shù)之前就已經(jīng)裝填仁连。
這樣就可以在執(zhí)行之前的函數(shù)前蓝角,做一些操作。

裝飾器的實(shí)質(zhì)是創(chuàng)建了一個(gè)新的函數(shù)饭冬,只是引用名是相同的使鹅,實(shí)際是不同的兩個(gè)函數(shù)
執(zhí)行新的函數(shù),實(shí)際是執(zhí)行閉包中的函數(shù)昌抠。
"""


# 不帶參數(shù)的裝飾器
def calTime(func):
    def cal():
        start_time = time.time()
        func()
        stop_time = time.time()
        print("執(zhí)行時(shí)間 %f" % (stop_time - start_time))

    return cal


@calTime  # 相當(dāng)于test = callTime(test) 在沒有調(diào)用函數(shù)之前就已經(jīng)裝填
def test():
    for i in range(100000):
        pass


test()


# 帶參數(shù)的裝飾器
def set_func(func):
    def call_func(a):
        print("驗(yàn)證權(quán)限----->1")
        func(a)

    return call_func


@set_func
def test_num(num):
    print("打印參數(shù):%d" % num)


test_num(10)


# 不定長參數(shù)的裝飾器
def set_func(func):
    def call_func(*args, **kwargs):
        print("驗(yàn)證權(quán)限----->1")
        # func(args,kwargs) 不能這么寫患朱,相當(dāng)于傳遞了兩個(gè)參數(shù):一個(gè)元組,一個(gè)字典
        func(*args, **kwargs)  # * ** 的作用是拆包炊苫,拆成一個(gè)一個(gè)的數(shù)據(jù)

    return call_func


@set_func
def test_num(num, *args, **kwargs):
    print("打印參數(shù):%d" % num)
    print("打印參數(shù):", args)
    print("打印參數(shù):", kwargs)


test_num(10)
test_num(10, 20)
test_num(10, 20, 30, k=3)


# 有返回值的裝飾器裁厅。裝飾沒有返回值的函數(shù)時(shí)冰沙,返回None,不會出現(xiàn)問題执虹。
# 通用的裝飾器
def set_func(func):
    def call_func(*args, **kwargs):
        print("驗(yàn)證權(quán)限----->1")
        # func(args,kwargs) 不能這么寫拓挥,相當(dāng)于傳遞了兩個(gè)參數(shù):一個(gè)元組,一個(gè)字典
        return func(*args, **kwargs)  # * ** 的作用是拆包袋励,拆成一個(gè)一個(gè)的數(shù)據(jù)

    return call_func


@set_func
def test_num(num, *args, **kwargs):
    print("打印參數(shù):%d" % num)
    print("打印參數(shù):", args)
    print("打印參數(shù):", kwargs)
    return "ok"


ret = test_num(10)
print(ret)


# 多個(gè)裝飾器裝飾一個(gè)函數(shù)
def add_qx(func):
    print("開始裝飾權(quán)限")

    def call_func(*args, **kwargs):
        print("增加權(quán)限")
        # func(args,kwargs) 不能這么寫侥啤,相當(dāng)于傳遞了兩個(gè)參數(shù):一個(gè)元組,一個(gè)字典
        return func(*args, **kwargs)  # * ** 的作用是拆包茬故,拆成一個(gè)一個(gè)的數(shù)據(jù)

    return call_func


def add_xx(func):
    print("開始裝飾功能")

    def call_func(*args, **kwargs):
        print("增加功能")
        # func(args,kwargs) 不能這么寫盖灸,相當(dāng)于傳遞了兩個(gè)參數(shù):一個(gè)元組,一個(gè)字典
        return func(*args, **kwargs)  # * ** 的作用是拆包磺芭,拆成一個(gè)一個(gè)的數(shù)據(jù)

    return call_func


# 裝填順序add_xx add_qx 先裝填距離函數(shù)最近的
# 執(zhí)行順序add_qx add_xx 最后裝填的先執(zhí)行糠雨,因?yàn)樽詈蟮闹赶蚴撬缓笠粚诱{(diào)用回去
@add_qx
@add_xx
def test_num(num, *args, **kwargs):
    print("打印參數(shù):%d" % num)
    return "ok"


ret = test_num(10)
print(ret)


# 練習(xí) 實(shí)現(xiàn) <h1>haha</h1>
def add_h1(func):
    def call_h1():
        return "<h1>" + func() + "</h1>"

    return call_h1


@add_h1
def test_string():
    return "haha"


print(test_string())

1徘跪、先明白這段代碼

#### 第一波 ####
def foo():
    print('foo')

foo  # 表示是函數(shù)
foo()  # 表示執(zhí)行foo函數(shù)

#### 第二波 ####
def foo():
    print('foo')

foo = lambda x: x + 1

foo()  # 執(zhí)行l(wèi)ambda表達(dá)式,而不再是原來的foo函數(shù)琅攘,因?yàn)閒oo這個(gè)名字被重新指向了另外一個(gè)匿名函數(shù)

函數(shù)名僅僅是個(gè)變量垮庐,只不過指向了定義的函數(shù)而已,所以才能通過 函數(shù)名()調(diào)用坞琴,如果 函數(shù)名=xxx被修改了哨查,那么當(dāng)在執(zhí)行 函數(shù)名()時(shí),調(diào)用的就不知之前的那個(gè)函數(shù)了

2剧辐、使用原因

寫代碼要遵循開放封閉原則寒亥,雖然在這個(gè)原則是用的面向?qū)ο箝_發(fā),但是也適用于函數(shù)式編程荧关,簡單來說溉奕,它規(guī)定已經(jīng)實(shí)現(xiàn)的功能代碼不允許被修改,但可以被擴(kuò)展忍啤,即:

  • 封閉:已實(shí)現(xiàn)的功能代碼塊
  • 開放:對擴(kuò)展開發(fā)

如果將開放封閉原則應(yīng)用在上述需求中加勤,那么就不允許在函數(shù) f1 、f2同波、f3鳄梅、f4的內(nèi)部進(jìn)行修改代碼,于是就有了下面的實(shí)現(xiàn)方案:

def w1(func):
    def inner():
        # 驗(yàn)證1
        # 驗(yàn)證2
        # 驗(yàn)證3
        func()
    return inner

@w1
def f1():
    print('f1')
@w1
def f2():
    print('f2')
@w1
def f3():
    print('f3')
@w1
def f4():
    print('f4')

對于上述代碼未檩,也是僅僅對基礎(chǔ)平臺的代碼進(jìn)行修改戴尸,就可以實(shí)現(xiàn)在其他人調(diào)用函數(shù) f1 f2 f3 f4 之前都進(jìn)行【驗(yàn)證】操作,并且無需做任何操作冤狡。

詳解:

單獨(dú)以f1為例:

def w1(func):
    def inner():
        # 驗(yàn)證1
        # 驗(yàn)證2
        # 驗(yàn)證3
        func()
    return inner

@w1
def f1():
    print('f1')

python解釋器就會從上到下解釋代碼孙蒙,步驟如下:

  1. def w1(func): ==>將w1函數(shù)加載到內(nèi)存
  2. @w1

沒錯(cuò)项棠, 從表面上看解釋器僅僅會解釋這兩句代碼,因?yàn)楹瘮?shù)在 沒有被調(diào)用之前其內(nèi)部代碼不會被執(zhí)行马篮。

從表面上看解釋器著實(shí)會執(zhí)行這兩句沾乘,但是 @w1 這一句代碼里卻有大文章, @函數(shù)名 是python的一種語法糖浑测。

上例@w1內(nèi)部會執(zhí)行一下操作:

執(zhí)行w1函數(shù)

執(zhí)行w1函數(shù) 黄痪,并將 @w1 下面的函數(shù)作為w1函數(shù)的參數(shù)俄精,即:**@w1 等價(jià)于 w1(f1) **所以,內(nèi)部就會去執(zhí)行:

def inner(): 
    #驗(yàn)證 1
    #驗(yàn)證 2
    #驗(yàn)證 3
    f1()    # func是參數(shù),此時(shí) func 等于 f1 
return inner# 返回的 inner烹卒,inner代表的是函數(shù),非執(zhí)行函數(shù) ,其實(shí)就是將原來的 f1 函數(shù)塞進(jìn)另外一個(gè)函數(shù)中

w1的返回值

將執(zhí)行完的w1函數(shù)返回值 賦值 給@w1下面的函數(shù)的函數(shù)名f1 即將w1的返回值再重新賦值給 f1盾计,即:

新f1 = def inner(): 
            #驗(yàn)證 1
            #驗(yàn)證 2
            #驗(yàn)證 3
            原來f1()
        return inner

所以搞糕,以后業(yè)務(wù)部門想要執(zhí)行 f1 函數(shù)時(shí),就會執(zhí)行 新f1 函數(shù)蜂科,在新f1 函數(shù)內(nèi)部先執(zhí)行驗(yàn)證顽决,再執(zhí)行原來的f1函數(shù),然后將原來f1 函數(shù)的返回值返回給了業(yè)務(wù)調(diào)用者导匣。

如此一來才菠, 即執(zhí)行了驗(yàn)證的功能,又執(zhí)行了原來f1函數(shù)的內(nèi)容贡定,并將原f1函數(shù)返回值 返回給業(yè)務(wù)調(diào)用著

Low BBB 你明白了嗎赋访?要是沒明白的話,我晚上去你家?guī)湍憬鉀Q吧;捍r镜ⅰ!

3. 再議裝飾器


# 定義函數(shù):完成包裹數(shù)據(jù)
def makeBold(fn):
    def wrapped():
        return "<b>" + fn() + "</b>"
    return wrapped

# 定義函數(shù):完成包裹數(shù)據(jù)
def makeItalic(fn):
    def wrapped():
        return "<i>" + fn() + "</i>"
    return wrapped

@makeBold
def test1():
    return "hello world-1"

@makeItalic
def test2():
    return "hello world-2"

@makeBold
@makeItalic
def test3():
    return "hello world-3"

print(test1())
print(test2())
print(test3())

運(yùn)行結(jié)果:

<b>hello world-1</b>
<i>hello world-2</i>
<b><i>hello world-3</i></b>

4. 裝飾器示例

例1:無參數(shù)的函數(shù)


from time import ctime, sleep

def timefun(func):
    def wrapped_func():
        print("%s called at %s" % (func.__name__, ctime()))
        func()
    return wrapped_func

@timefun
def foo():
    print("I am foo")

foo()
sleep(2)
foo()

上面代碼理解裝飾器執(zhí)行行為可理解成

foo = timefun(foo)
# foo先作為參數(shù)賦值給func后,foo接收指向timefun返回的wrapped_func
foo()
# 調(diào)用foo(),即等價(jià)調(diào)用wrapped_func()
# 內(nèi)部函數(shù)wrapped_func被引用旋炒,所以外部函數(shù)的func變量(自由變量)并沒有釋放
# func里保存的是原foo函數(shù)對象

例2:被裝飾的函數(shù)有參數(shù)

from time import ctime, sleep

def timefun(func):
    def wrapped_func(a, b):
        print("%s called at %s" % (func.__name__, ctime()))
        print(a, b)
        func(a, b)
    return wrapped_func

@timefun
def foo(a, b):
    print(a+b)

foo(3,5)
sleep(2)
foo(2,4)

例3:被裝飾的函數(shù)有不定長參數(shù)

from time import ctime, sleep

def timefun(func):
    def wrapped_func(*args, **kwargs):
        print("%s called at %s"%(func.__name__, ctime()))
        func(*args, **kwargs)
    return wrapped_func

@timefun
def foo(a, b, c):
    print(a+b+c)

foo(3,5,7)
sleep(2)
foo(2,4,9)

例4:裝飾器中的return

from time import ctime, sleep

def timefun(func):
    def wrapped_func():
        print("%s called at %s" % (func.__name__, ctime()))
        func()
    return wrapped_func

@timefun
def foo():
    print("I am foo")

@timefun
def getInfo():
    return '----hahah---'

foo()
sleep(2)
foo()

print(getInfo())

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

foo called at Fri Nov  4 21:55:35 2016
I am foo
foo called at Fri Nov  4 21:55:37 2016
I am foo
getInfo called at Fri Nov  4 21:55:37 2016
None

如果修改裝飾器為return func()步悠,則運(yùn)行結(jié)果:

foo called at Fri Nov  4 21:55:57 2016
I am foo
foo called at Fri Nov  4 21:55:59 2016
I am foo
getInfo called at Fri Nov  4 21:55:59 2016
----hahah---

總結(jié):

  • 一般情況下為了讓裝飾器更通用,可以有return

例5:裝飾器帶參數(shù),在原有裝飾器的基礎(chǔ)上瘫镇,設(shè)置外部變量

#decorator2.py

from time import ctime, sleep

def timefun_arg(pre="hello"):
    def timefun(func):
        def wrapped_func():
            print("%s called at %s %s" % (func.__name__, ctime(), pre))
            return func()
        return wrapped_func
    return timefun

# 下面的裝飾過程
# 1\. 調(diào)用timefun_arg("itcast")
# 2\. 將步驟1得到的返回值贤徒,即time_fun返回, 然后time_fun(foo)
# 3\. 將time_fun(foo)的結(jié)果返回汇四,即wrapped_func
# 4\. 讓foo = wrapped_fun接奈,即foo現(xiàn)在指向wrapped_func
@timefun_arg("itcast")
def foo():
    print("I am foo")

@timefun_arg("python")
def too():
    print("I am too")

foo()
sleep(2)
foo()

too()
sleep(2)
too()

可以理解為

foo()==timefun_arg("itcast")(foo)()

例6:類裝飾器(擴(kuò)展,非重點(diǎn))

裝飾器函數(shù)其實(shí)是這樣一個(gè)接口約束通孽,它必須接受一個(gè)callable對象作為參數(shù)序宦,然后返回一個(gè)callable對象。在Python中一般callable對象都是函數(shù)背苦,但也有例外互捌。只要某個(gè)對象重寫了 __call__() 方法潘明,那么這個(gè)對象就是callable的。

class Test():
    def __call__(self):
        print('call me!')

t = Test()
t()  # call me

類裝飾器demo


class Test(object):
    def __init__(self, func):
        print("---初始化---")
        print("func name is %s"%func.__name__)
        self.__func = func
    def __call__(self):
        print("---裝飾器中的功能---")
        self.__func()
#說明:
#1\. 當(dāng)用Test來裝作裝飾器對test函數(shù)進(jìn)行裝飾的時(shí)候秕噪,首先會創(chuàng)建Test的實(shí)例對象
#   并且會把test這個(gè)函數(shù)名當(dāng)做參數(shù)傳遞到__init__方法中
#   即在__init__方法中的屬性__func指向了test指向的函數(shù)
#
#2\. test指向了用Test創(chuàng)建出來的實(shí)例對象
#
#3\. 當(dāng)在使用test()進(jìn)行調(diào)用時(shí)钳降,就相當(dāng)于讓這個(gè)對象(),因此會調(diào)用這個(gè)對象的__call__方法
#
#4\. 為了能夠在__call__方法中調(diào)用原來test指向的函數(shù)體腌巾,所以在__init__方法中就需要一個(gè)實(shí)例屬性來保存這個(gè)函數(shù)體的引用
#   所以才有了self.__func = func這句代碼遂填,從而在調(diào)用__call__方法中能夠調(diào)用到test之前的函數(shù)體
@Test
def test():
    print("----test---")
test()
showpy()#如果把這句話注釋,重新運(yùn)行程序澈蝙,依然會看到"--初始化--"

運(yùn)行結(jié)果如下:

---初始化---
func name is test
---裝飾器中的功能---
----test---
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吓坚,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子灯荧,更是在濱河造成了極大的恐慌礁击,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逗载,死亡現(xiàn)場離奇詭異哆窿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)厉斟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門挚躯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人捏膨,你說我怎么就攤上這事∈澄辏” “怎么了号涯?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長锯七。 經(jīng)常有香客問我链快,道長,這世上最難降的妖魔是什么眉尸? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任域蜗,我火速辦了婚禮,結(jié)果婚禮上噪猾,老公的妹妹穿的比我還像新娘霉祸。我一直安慰自己,他們只是感情好袱蜡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布丝蹭。 她就那樣靜靜地躺著,像睡著了一般坪蚁。 火紅的嫁衣襯著肌膚如雪奔穿。 梳的紋絲不亂的頭發(fā)上镜沽,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天,我揣著相機(jī)與錄音贱田,去河邊找鬼缅茉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛男摧,可吹牛的內(nèi)容都是我干的蔬墩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼彩倚,長吁一口氣:“原來是場噩夢啊……” “哼筹我!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起帆离,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蔬蕊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后哥谷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體岸夯,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年们妥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了猜扮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,127評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡监婶,死狀恐怖旅赢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情惑惶,我是刑警寧澤煮盼,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站带污,受9級特大地震影響僵控,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鱼冀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一报破、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧千绪,春花似錦充易、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春鹉究,著一層夾襖步出監(jiān)牢的瞬間宇立,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工自赔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留妈嘹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓润脸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親毙驯。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評論 2 355

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

  • 裝飾器引入 初期及問題誕生 假如現(xiàn)在在一個(gè)公司爆价,有A B C三個(gè)業(yè)務(wù)部門,還有S一個(gè)基礎(chǔ)服務(wù)部門媳搪,目前呢铭段,S部門提...
    pubalabala閱讀 225評論 0 0
  • 1.1裝飾器 裝飾器是程序開發(fā)中經(jīng)常會用到的一個(gè)功能秦爆,用好了裝飾器,開發(fā)效率如虎添翼等限,所以這也是Python面試中...
    PythonMaO閱讀 482評論 1 5
  • 上一篇文章為:→1.1.6閉包 裝飾器 裝飾器是程序開發(fā)中經(jīng)常會用到的一個(gè)功能望门,用好了裝飾器形娇,開發(fā)效率如虎添翼,所...
    lyh165閱讀 316評論 1 1
  • 本節(jié)課綱: 魔法方法之_call_ 閉包 裝飾器 裝飾器實(shí)例 一埂软、魔法方法之_call_ 在Python中纫事,函數(shù)其...
    郭_揚(yáng)閱讀 420評論 0 0
  • 寒夜中佇立仰望所灸,那點(diǎn)星光,乍隱乍現(xiàn)伴隨著隱隱思緒一片葉緩緩而落摩挲爬立,沉思這葉是因?yàn)槲业耐A暨€是,秋日的一去不返
    素錦醉閱讀 217評論 2 1