中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

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

發布時間:2020-08-20 08:54:09 來源:腳本之家 閱讀:160 作者:小洋人最happy 欄目:開發技術

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

裝飾器引入

初期及問題誕生

假如現在在一個公司,有A B C三個業務部門,還有S一個基礎服務部門,目前呢,S部門提供了兩個函數,供其他部門調用,函數如下:

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


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

在初期,其他部門這樣調用是沒有問題的,隨著公司業務的發展,現在S部門需要對函數調用假如權限驗證,如果有權限的話,才能進行調用,否則調用失敗。考慮一下,如果是我們,該怎么做呢?

方案集合

  1. 讓調用方也就是ABC部門在調用的時候,先主動進行權限驗證
  2. S部門在對外提供的函數中,首先進行權限認證,然后再進行真正的函數操作

問題

  1. 方案一,將本不該暴露給外層的權限認證,暴露在使用方面前,同時如果有多個部門呢,要每個部門每個人都要周知到,你還不缺定別人一定會這么做,不靠譜。。。
  2. 方案二,看似看行,可是當S部門對外提供更多的需要進行權限驗證方法時,每個函數都要調用權限驗證,同樣也實在費勁,不利于代碼的維護性和擴展性

那么,有沒有一種方法能夠遵循代碼的開放閉合原則,來完美的解決此問題呢?

裝飾器引入

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

def w1(func):
  def inner():
    print('...驗證權限...')
    func()

  return inner


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


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


f1()
f2()

輸出結果為

...驗證權限...
f1 called
...驗證權限...
f2 called

可以通過代碼及輸出看到,在調用f1 f2 函數時,成功進行了權限驗證,那么是怎么做到的呢?其實這里就使用到了裝飾器,通過定義一個閉包函數w1,在我們調用函數上通過關鍵詞@w1,這樣就對f1 f2函數完成了裝飾。

裝飾器原理

首先,開看我們的裝飾器函數w1,該函數接收一個參數func,其實就是接收一個方法名,w1內部又定義一個函數inner,在inner函數中增加權限校驗,并在驗證完權限后調用傳進來的參數func,同時w1的返回值為內部函數inner,其實就是一個閉包函數。

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

接下來,在調用f1()的時候,其實調用的是w1.inner函數,那么此時就會先執行權限驗證,然后再調用原來的f1(),該處的f1就是通過裝飾傳進來的參數f1。

這樣下來,就完成了對f1的裝飾,實現了權限驗證。

裝飾器知識點

執行時機

了解了裝飾器的原理后,那么它的執行時機是什么樣呢,接下來就來看一下。
國際慣例,先上代碼

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

  def inner():
    print('...驗證權限...')
    fun()

  return inner


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

test()

輸出結果為

...裝飾器開始裝飾...
...驗證權限...
test

由此可以發現,當python解釋器執行到@w1時,就開始進行裝飾了,相當于執行了如下代碼:

test = w1(test)

兩個裝飾器執行流程和裝飾結果

當有兩個或兩個以上裝飾器裝飾一個函數時,那么執行流程和裝飾結果是什么樣的呢?同樣,還是以代碼來說明問題。

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)

輸出結果:

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

可以發現,先用第二個裝飾器(makeItalic)進行裝飾,接著再用第一個裝飾器(makeBold)進行裝飾,而在調用過程中,先執行第一個裝飾器(makeBold),接著再執行第二個裝飾器(makeItalic)。

為什么呢,分兩步來分析一下。

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

對無參函數進行裝飾

上面例子中的f1 f2都是對無參函數的裝飾,不再單獨舉例

對有參函數進行裝飾

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

代碼優先:

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

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

  return inner


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


hello('wangcai')

輸出結果為:

say inner called
hello wangcai

具體說明代碼注釋已經有了,就不再單獨說明了。
此時,也許你就會問了,那是一個參數的,如果多個或者不定長參數呢,該如何處理呢?看看下面的代碼你就秒懂了。

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)

輸出結果為:

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

利用python的可變參數輕松實現裝飾帶參數的函數。

對帶返回值的函數進行裝飾

下面對有返回值的函數進行裝飾,按照之前的寫法,代碼是這樣的

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)

輸出結果為:

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

可以發現,此時,并沒有輸出test函數的‘hello',而是None,那是為什么呢,可以發現,在inner函數中對test進行了調用,但是沒有接受不了返回值,也沒有進行返回,那么默認就是None了,知道了原因,那么來修改一下代碼:

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)

輸出結果:

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

這樣就達到預期,完成對帶返回值參數的函數進行裝飾。

帶參數的裝飾器

介紹了對帶參數的函數和有返回值的函數進行裝飾,那么有沒有帶參數的裝飾器呢,如果有的話,又有什么用呢?
答案肯定是有的,接下來通過代碼來看一下吧。

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

    return inner

  return w_test_log


# 帶有參數的裝飾器能夠起到在運行時,有不同的功能

# 先執行func_args('wangcai'),返回w_test_log函數的引用
# @w_test_log
# 使用@w_test_log對test_log進行裝飾
@func_args('wangcai')
def test_log():
  print('this is test log')


test_log()

輸出結果為:

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

簡單理解,帶參數的裝飾器就是在原閉包的基礎上又加了一層閉包,通過外層函數func_args的返回值w_test_log就看出來了,具體執行流程在注釋里已經說明了。
好處就是可以在運行時,針對不同的參數做不同的應用功能處理。

通用裝飾器

介紹了這么多,在實際應用中,如果針對沒個類別的函數都要寫一個裝飾器的話,估計就累死了,那么有沒有通用萬能裝飾器呢,答案肯定是有的,廢話不多說,直接上代碼。

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

把上面幾種示例結合起來,就完成了通用裝飾器的功能,原理都同上,就不過多廢話了。

類裝飾器

裝飾器函數其實是一個接口約束,它必須接受一個callable對象作為參數,然后返回一個callable對象。
在python中,一般callable對象都是函數,但是也有例外。比如只要某個對象重寫了call方法,那么這個對象就是callable的。
當創建一個對象后,直接去執行這個對象,那么是會拋出異常的,因為他不是callable,無法直接執行,但進行修改后,就可以直接執行調用了,如下

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


t = Test()
print(t())

輸出為:

call called

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

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

輸出結果為:

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

和之前的原理一樣,當python解釋器執行到到@Test時,會把當前test函數作為參數傳入Test對象,調用init方法,同時將test函數指向創建的Test對象,那么在接下來執行test()的時候,其實就是直接對創建的對象進行調用,執行其call方法。

好了,到目前為止,基本把python裝飾器及相關知識點講完了,如有問題,歡迎指出哈~

以上所述是小編給大家介紹的python裝飾器簡介詳解整合,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

仲巴县| 封丘县| 于都县| 屏山县| 邢台县| 仪征市| 上高县| 响水县| 玉环县| 大同县| 高阳县| 合水县| 武威市| 始兴县| 修水县| 临汾市| 安康市| 汉源县| 左权县| 广平县| 伊川县| 牟定县| 宁海县| 吴堡县| 寿阳县| 磐安县| 沙洋县| 新和县| 中超| 富锦市| 项城市| 玛曲县| 廊坊市| 上蔡县| 宜丰县| 宣恩县| 寿宁县| 恩施市| 商城县| 和龙市| 淮南市|