溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

python裝飾器簡(jiǎn)介---這一篇也許就夠了(推薦)

發(fā)布時(shí)間:2020-08-20 08:54:09 來(lái)源:腳本之家 閱讀:151 作者:小洋人最happy 欄目:開(kāi)發(fā)技術(shù)

Python裝飾器(decorator)是在程序開(kāi)發(fā)中經(jīng)常使用到的功能,合理使用裝飾器,能讓我們的程序如虎添翼。

裝飾器引入

初期及問(wèn)題誕生

假如現(xiàn)在在一個(gè)公司,有A B C三個(gè)業(yè)務(wù)部門,還有S一個(gè)基礎(chǔ)服務(wù)部門,目前呢,S部門提供了兩個(gè)函數(shù),供其他部門調(diào)用,函數(shù)如下:

def f1():
  print('f1 called')


def f2():
  print('f2 called')

在初期,其他部門這樣調(diào)用是沒(méi)有問(wèn)題的,隨著公司業(yè)務(wù)的發(fā)展,現(xiàn)在S部門需要對(duì)函數(shù)調(diào)用假如權(quán)限驗(yàn)證,如果有權(quán)限的話,才能進(jìn)行調(diào)用,否則調(diào)用失敗??紤]一下,如果是我們,該怎么做呢?

方案集合

  1. 讓調(diào)用方也就是ABC部門在調(diào)用的時(shí)候,先主動(dòng)進(jìn)行權(quán)限驗(yàn)證
  2. S部門在對(duì)外提供的函數(shù)中,首先進(jìn)行權(quán)限認(rèn)證,然后再進(jìn)行真正的函數(shù)操作

問(wèn)題

  1. 方案一,將本不該暴露給外層的權(quán)限認(rèn)證,暴露在使用方面前,同時(shí)如果有多個(gè)部門呢,要每個(gè)部門每個(gè)人都要周知到,你還不缺定別人一定會(huì)這么做,不靠譜。。。
  2. 方案二,看似看行,可是當(dāng)S部門對(duì)外提供更多的需要進(jìn)行權(quán)限驗(yàn)證方法時(shí),每個(gè)函數(shù)都要調(diào)用權(quán)限驗(yàn)證,同樣也實(shí)在費(fèi)勁,不利于代碼的維護(hù)性和擴(kuò)展性

那么,有沒(méi)有一種方法能夠遵循代碼的開(kāi)放閉合原則,來(lái)完美的解決此問(wèn)題呢?

裝飾器引入

答案肯定是有的,不然真的是弱爆了。先看代碼

def w1(func):
  def inner():
    print('...驗(yàn)證權(quán)限...')
    func()

  return inner


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


@w1
def f2():
  print('f2 called')


f1()
f2()

輸出結(jié)果為

...驗(yàn)證權(quán)限...
f1 called
...驗(yàn)證權(quán)限...
f2 called

可以通過(guò)代碼及輸出看到,在調(diào)用f1 f2 函數(shù)時(shí),成功進(jìn)行了權(quán)限驗(yàn)證,那么是怎么做到的呢?其實(shí)這里就使用到了裝飾器,通過(guò)定義一個(gè)閉包函數(shù)w1,在我們調(diào)用函數(shù)上通過(guò)關(guān)鍵詞@w1,這樣就對(duì)f1 f2函數(shù)完成了裝飾。

裝飾器原理

首先,開(kāi)看我們的裝飾器函數(shù)w1,該函數(shù)接收一個(gè)參數(shù)func,其實(shí)就是接收一個(gè)方法名,w1內(nèi)部又定義一個(gè)函數(shù)inner,在inner函數(shù)中增加權(quán)限校驗(yàn),并在驗(yàn)證完權(quán)限后調(diào)用傳進(jìn)來(lái)的參數(shù)func,同時(shí)w1的返回值為內(nèi)部函數(shù)inner,其實(shí)就是一個(gè)閉包函數(shù)。

然后,再來(lái)看一下,在f1上增加@w1,那這是什么意思呢?當(dāng)python解釋器執(zhí)行到這句話的時(shí)候,會(huì)去調(diào)用w1函數(shù),同時(shí)將被裝飾的函數(shù)名作為參數(shù)傳入(此時(shí)為f1),根據(jù)閉包一文分析,在執(zhí)行w1函數(shù)的時(shí)候,此時(shí)直接把inner函數(shù)返回了,同時(shí)把它賦值給f1,此時(shí)的f1已經(jīng)不是未加裝飾時(shí)的f1了,而是指向了w1.inner函數(shù)地址。

接下來(lái),在調(diào)用f1()的時(shí)候,其實(shí)調(diào)用的是w1.inner函數(shù),那么此時(shí)就會(huì)先執(zhí)行權(quán)限驗(yàn)證,然后再調(diào)用原來(lái)的f1(),該處的f1就是通過(guò)裝飾傳進(jìn)來(lái)的參數(shù)f1。

這樣下來(lái),就完成了對(duì)f1的裝飾,實(shí)現(xiàn)了權(quán)限驗(yàn)證。

裝飾器知識(shí)點(diǎn)

執(zhí)行時(shí)機(jī)

了解了裝飾器的原理后,那么它的執(zhí)行時(shí)機(jī)是什么樣呢,接下來(lái)就來(lái)看一下。
國(guó)際慣例,先上代碼

def w1(fun):
  print('...裝飾器開(kāi)始裝飾...')

  def inner():
    print('...驗(yàn)證權(quán)限...')
    fun()

  return inner


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

test()

輸出結(jié)果為

...裝飾器開(kāi)始裝飾...
...驗(yàn)證權(quán)限...
test

由此可以發(fā)現(xiàn),當(dāng)python解釋器執(zhí)行到@w1時(shí),就開(kāi)始進(jìn)行裝飾了,相當(dāng)于執(zhí)行了如下代碼:

test = w1(test)

兩個(gè)裝飾器執(zhí)行流程和裝飾結(jié)果

當(dāng)有兩個(gè)或兩個(gè)以上裝飾器裝飾一個(gè)函數(shù)時(shí),那么執(zhí)行流程和裝飾結(jié)果是什么樣的呢?同樣,還是以代碼來(lái)說(shuō)明問(wèn)題。

def makeBold(fun):
  print('----a----')

  def inner():
    print('----1----')
    return '<b>' + fun() + '</b>'

  return inner


def makeItalic(fun):
  print('----b----')

  def inner():
    print('----2----')
    return '<i>' + fun() + '</i>'

  return inner


@makeBold
@makeItalic
def test():
  print('----c----')
  print('----3----')
  return 'hello python decorator'


ret = test()
print(ret)

輸出結(jié)果:

----b----
----a----
----1----
----2----
----c----
----3----
<b><i>hello python decorator</i></b>

可以發(fā)現(xiàn),先用第二個(gè)裝飾器(makeItalic)進(jìn)行裝飾,接著再用第一個(gè)裝飾器(makeBold)進(jìn)行裝飾,而在調(diào)用過(guò)程中,先執(zhí)行第一個(gè)裝飾器(makeBold),接著再執(zhí)行第二個(gè)裝飾器(makeItalic)。

為什么呢,分兩步來(lái)分析一下。

  1. 裝飾時(shí)機(jī) 通過(guò)上面裝飾時(shí)機(jī)的介紹,我們可以知道,在執(zhí)行到@makeBold的時(shí)候,需要對(duì)下面的函數(shù)進(jìn)行裝飾,此時(shí)解釋器繼續(xù)往下走,發(fā)現(xiàn)并不是一個(gè)函數(shù)名,而又是一個(gè)裝飾器,這時(shí)候,@makeBold裝飾器暫停執(zhí)行,而接著執(zhí)行接下來(lái)的裝飾器@makeItalic,接著把test函數(shù)名傳入裝飾器函數(shù),從而打印'b',在makeItalic裝飾完后,此時(shí)的test指向makeItalic的inner函數(shù)地址,這時(shí)候有返回來(lái)執(zhí)行@makeBold,接著把新test傳入makeBold裝飾器函數(shù)中,因此打印了'a'。
  2. 在調(diào)用test函數(shù)的時(shí)候,根據(jù)上述分析,此時(shí)test指向makeBold.inner函數(shù),因此會(huì)先打印‘1‘,接下來(lái),在調(diào)用fun()的時(shí)候,其實(shí)是調(diào)用的makeItalic.inner()函數(shù),所以打印‘2‘,在makeItalic.inner中,調(diào)用的fun其實(shí)才是我們最原聲的test函數(shù),所以打印原test函數(shù)中的‘c‘,‘3‘,所以在一層層調(diào)完之后,打印的結(jié)果為<b><i>hello python decorator</i></b> 。

對(duì)無(wú)參函數(shù)進(jìn)行裝飾

上面例子中的f1 f2都是對(duì)無(wú)參函數(shù)的裝飾,不再單獨(dú)舉例

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

在使用中,有的函數(shù)可能會(huì)帶有參數(shù),那么這種如何處理呢?

代碼優(yōu)先:

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

  def inner(name):
    """
    如果被裝飾的函數(shù)有行參,那么閉包函數(shù)必須有參數(shù)
    :param name:
    :return:
    """
    print('say inner called')
    fun(name)

  return inner


@w_say
def hello(name):
  print('hello ' + name)


hello('wangcai')

輸出結(jié)果為:

say inner called
hello wangcai

具體說(shuō)明代碼注釋已經(jīng)有了,就不再單獨(dú)說(shuō)明了。
此時(shí),也許你就會(huì)問(wèn)了,那是一個(gè)參數(shù)的,如果多個(gè)或者不定長(zhǎng)參數(shù)呢,該如何處理呢?看看下面的代碼你就秒懂了。

def w_add(func):
  def inner(*args, **kwargs):
    print('add inner called')
    func(*args, **kwargs)

  return inner


@w_add
def add(a, b):
  print('%d + %d = %d' % (a, b, a + b))


@w_add
def add2(a, b, c):
  print('%d + %d + %d = %d' % (a, b, c, a + b + c))


add(2, 4)
add2(2, 4, 6)

輸出結(jié)果為:

add inner called
2 + 4 = 6
add inner called
2 + 4 + 6 = 12

利用python的可變參數(shù)輕松實(shí)現(xiàn)裝飾帶參數(shù)的函數(shù)。

對(duì)帶返回值的函數(shù)進(jìn)行裝飾

下面對(duì)有返回值的函數(shù)進(jìn)行裝飾,按照之前的寫(xiě)法,代碼是這樣的

def w_test(func):
  def inner():
    print('w_test inner called start')
    func()
    print('w_test inner called end')
  return inner


@w_test
def test():
  print('this is test fun')
  return 'hello'


ret = test()
print('ret value is %s' % ret)

輸出結(jié)果為:

w_test inner called start
this is test fun
w_test inner called end
ret value is None

可以發(fā)現(xiàn),此時(shí),并沒(méi)有輸出test函數(shù)的‘hello',而是None,那是為什么呢,可以發(fā)現(xiàn),在inner函數(shù)中對(duì)test進(jìn)行了調(diào)用,但是沒(méi)有接受不了返回值,也沒(méi)有進(jìn)行返回,那么默認(rèn)就是None了,知道了原因,那么來(lái)修改一下代碼:

def w_test(func):
  def inner():
    print('w_test inner called start')
    str = func()
    print('w_test inner called end')
    return str

  return inner


@w_test
def test():
  print('this is test fun')
  return 'hello'


ret = test()
print('ret value is %s' % ret)

輸出結(jié)果:

w_test inner called start
this is test fun
w_test inner called end
ret value is hello

這樣就達(dá)到預(yù)期,完成對(duì)帶返回值參數(shù)的函數(shù)進(jìn)行裝飾。

帶參數(shù)的裝飾器

介紹了對(duì)帶參數(shù)的函數(shù)和有返回值的函數(shù)進(jìn)行裝飾,那么有沒(méi)有帶參數(shù)的裝飾器呢,如果有的話,又有什么用呢?
答案肯定是有的,接下來(lái)通過(guò)代碼來(lái)看一下吧。

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()

輸出結(jié)果為:

...記錄日志...visitor is wangcai
this is test log

簡(jiǎn)單理解,帶參數(shù)的裝飾器就是在原閉包的基礎(chǔ)上又加了一層閉包,通過(guò)外層函數(shù)func_args的返回值w_test_log就看出來(lái)了,具體執(zhí)行流程在注釋里已經(jīng)說(shuō)明了。
好處就是可以在運(yùn)行時(shí),針對(duì)不同的參數(shù)做不同的應(yīng)用功能處理。

通用裝飾器

介紹了這么多,在實(shí)際應(yīng)用中,如果針對(duì)沒(méi)個(gè)類別的函數(shù)都要寫(xiě)一個(gè)裝飾器的話,估計(jì)就累死了,那么有沒(méi)有通用萬(wàn)能裝飾器呢,答案肯定是有的,廢話不多說(shuō),直接上代碼。

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)

輸出結(jié)果為:

test called
test1 called
test2 called and value is 9

把上面幾種示例結(jié)合起來(lái),就完成了通用裝飾器的功能,原理都同上,就不過(guò)多廢話了。

類裝飾器

裝飾器函數(shù)其實(shí)是一個(gè)接口約束,它必須接受一個(gè)callable對(duì)象作為參數(shù),然后返回一個(gè)callable對(duì)象。
在python中,一般callable對(duì)象都是函數(shù),但是也有例外。比如只要某個(gè)對(duì)象重寫(xiě)了call方法,那么這個(gè)對(duì)象就是callable的。
當(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())

輸出為:

call called

下面,引入正題,看一下如何用類裝飾函數(shù)。

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('裝飾器中的功能')
    self.__func()


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


test()

輸出結(jié)果為:

test init
func name is test
裝飾器中的功能
this is test func

和之前的原理一樣,當(dāng)python解釋器執(zhí)行到到@Test時(shí),會(huì)把當(dāng)前test函數(shù)作為參數(shù)傳入Test對(duì)象,調(diào)用init方法,同時(shí)將test函數(shù)指向創(chuàng)建的Test對(duì)象,那么在接下來(lái)執(zhí)行test()的時(shí)候,其實(shí)就是直接對(duì)創(chuàng)建的對(duì)象進(jìn)行調(diào)用,執(zhí)行其call方法。

好了,到目前為止,基本把python裝飾器及相關(guān)知識(shí)點(diǎn)講完了,如有問(wèn)題,歡迎指出哈~

以上所述是小編給大家介紹的python裝飾器簡(jiǎn)介詳解整合,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)億速云網(wǎng)站的支持!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI