1. 程式人生 > >python進階--裝飾器

python進階--裝飾器

打從大三學了python後, 就沒有學習python語法了, 在此之前學的都是和其他語言一樣的語法, 自此在做專案的時候遇到好多python高階語法的知識, 現在再來系統的學習一下, 省的總是百度. 先來說說python的裝飾器吧.

為什麼會有裝飾器這個奇怪的東西呢?  有時候是不是會覺得一個框架的功能缺少你想要的功能, 但是呢, 你總不能改人家的框架吧, 又想增加自己的功能, 這時裝飾器就是你的不二選擇, 顧名思義就是起裝飾作用的一坨程式碼. 來看看官方解釋吧.

裝飾器本質上是一個Python函式,它可以讓其他函式在不需要做任何程式碼變動的前提下增加額外功能,裝飾器的返回值也是一個函式物件。它經常用於有切面需求的場景,比如:插入日誌、效能測試、事務處理、快取、許可權校驗等場景。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量與函式功能本身無關的雷同程式碼並繼續重用。

先來看看早期的版本python2.4以下的裝飾器是怎麼寫的,先從一個簡單的例子說起, 比如我想寫個方法, 讓他輸出hello world 相比大家都會寫吧,  這可是程式設計師的第一課

def fun():
    print('Hello world')
fun()

然後如果我想要在列印helloworld前列印#####################呢? 可能有些會直接在print前列印, 這裡介紹優雅的寫法

def fun():
    print('Hello world')

def  Decorator(func):
    def test():
        print('#####################')
        return func()
    return test()

Decorator(fun)

這個程式碼的執行流程在這分析一下: 程式碼中定義了兩個方法, 最後呼叫了後面的方法, 著重分析後面的方法, 後面的方法傳入一個函式, 而在方法裡面又定義了一個函式, 定義玩後在return 這個函式, 則先執行這個函式, 然後在退出, 我們看看這個函式做了啥事, 首先列印一串#, 之後再return傳入的函式引數, 像當於呼叫了傳入的函式.,看一下結果

這是不是就是我們說到的裝飾器, 傳入函式, 返回函式

接下來我們做一點小改變

def fun():
    print('Hello world')

def  Decorator(func):
    def test():
        print('#####################')
        return func()
    return test

fun()
fun = Decorator(fun)
fun()

猜猜結果吧?  喝口水來

 

直接看結果吧:

是否很驚訝??  前後的fun()函式變了嗎?  這裡說個小細節, 就是後面那個方法返回的是test 而不是像前面的例子為test(), 那返回這個會不會呼叫test函數了???  答案是不會的,  只是把函式返回而非呼叫,  要明白return test 和return test()的區別, 這裡在說一下, 如果把上面的return func() 換成return func 那結果會變成什麼?? 我們來實驗一下把,

是否會感到迷惑,  helloworld怎麼沒列印了??  這是不是和我們上面說的一樣, return func()  才會呼叫func() returnfunc不會呼叫func()函式, 在這裡我們還是返回return func(), 在這呼叫了一下Decorator方法後fun的功能是不是改變了?  是不是增加了列印#的功能?  那我們有沒有改變fun的原始碼實現的這個功能, 明顯溼沒有的吧,  這就是裝飾器!!!! 在python2.4以後的版本引入了@符號, 表示呼叫裝飾器,  只要在需要增加功能的方法前用@呼叫裝飾器, 那就可以增加功能, 比如上面的寫法可以寫成

def  Decorator(func):
    def test():
        print('#####################')
        return func()
    return test

@Decorator
def fun():
    print('Hello world')

fun()

這裡需要注意的是: 裝飾器一定要在需要裝飾的方法前定義或者引入相應的裝飾器模組, 不然會報錯!!!!!!

是否會想這不是函式嗎, 函式一般都可以加引數吧, 那有引數的裝飾器怎麼寫???  下面引入帶引數的裝飾器寫法:  在寫程式碼的過程中, 遇到一個問題, 裝飾器是不是不能改變函式的引數, 那麼裝飾前後得一模一樣, 那麼return的時候也得返回引數,  有思路了

def  Decorator(func):
    def test(*args, **kwargs):
        print('#####################')
        return func(*args, **kwargs)
    return test

@Decorator
def fun(i):
    print(i)

fun(1)

輸出結果:

這裡 可能有些同學不太熟悉(*args, **kwargs)的用法, 這個等會我們在細說, 現在只要只要這是可以傳多個引數的就行了, 看上面的程式碼執行順序是, 先定義了裝飾器, 然後裝飾fun方法, 最後呼叫裝飾後的fun方法, 這裡著重說引數傳遞, 裝飾器把test方法返回, 先從原始寫法看吧  fun = Decorator(fun), 呼叫Decorator方法, 遇到定義方法, 先不過, 再遇到return test, 像當於fun=test, 而 那麼是不是test和fun傳的引數要一模一樣, 不然會出bug? 這就是寫法中的(*args, **kwargs)的引數在test和func中一模一樣. 這裡我們是不是有一個框架了, 可以傳入多個引數的裝飾器框架, 只要根據傳入的引數實現不同的功能, 或者直接實現不同的功能, 那麼功能程式碼應該替換print('#####################')程式碼段就可以了, 接下來我們根據傳入的引數, 來列印相應的引數值吧,

def  Decorator(func):
    def test(*args, **kwargs):
        print(*args)
        return func(*args, **kwargs)
    return test

@Decorator
def fun(i):
    print(i)

fun(1)

結果溼列印了兩次1,  到此, 裝飾器就講到此, 最後以一個通用模板的裝飾器結束

def  Decorator(func):
    def test(*args, **kwargs):
        ########users add fun#######
        print(*args)
         ########users add fun end#######
        return func(*args, **kwargs)
    return test

@Decorator
def fun(*args, **kwargs):
    #####fun part#######
    pass

fun(1, 2, 3)

其中: users add fun為需要為fun函式增添功能的程式碼段到users add fun end 結束, 而fun函式, 表示要裝飾函式的原始碼或者從其他模組呼叫的方法

--------------------------------------------------------華麗的分割線--------------------------------------------------------------------------------------------------------

接下來我們說說*args 和**kwargs 的使用方法

當函式的引數不確定時,可以使用*args 和**kwargs,*args 沒有key值,**kwargs有key值。當定義了這兩個後, 可以傳入多個引數, 不過最好不要多於7個, 直接上程式碼

def fun_var_args(farg, *args):  
    print "arg:", farg  
    for value in args:  
        print "another arg:", value  
  
fun_var_args(1, "two", 3) # *args可以當作可容納多個變數組成的list

結果:

arg: 1 
another arg: two 
another arg: 3 

程式碼e二:

def fun_var_kwargs(farg, **kwargs):  
    print "arg:", farg  
    for key in kwargs:  
        print "another keyword arg: %s: %s" % (key, kwargs[key])  
  
  
fun_var_kwargs(farg=1, myarg2="two", myarg3=3) # myarg2和myarg3被視為key, 感覺**kwargs可以當作容納多個key和value的dictionary

結果:

arg: 1  
another keyword arg: myarg2: two  
another keyword arg: myarg3: 3

看到這是不是明白了, **kwargs用法其實就是當傳入key=key-value時, 而很多的鍵和值儲存在kwargs字典中, 需要逐個訪問引數, 其中key-value=kwargs[key]

總結:

1、*args和**kwargs主要用於定義函式的可變引數

2、*args:傳送一個非鍵值對的可變數量的引數列表給函式

3、**kwargs:傳送一個鍵值對的可變數量的引數列表給函式

4、如果想要在函式內使用帶有名稱的變數(像字典那樣),那麼使用**kwargs。

定義可變引數的目的是為了簡化呼叫。

5、*args和**kwargs不是固定的,只有前面的*和**是固定不可變的,後面的名稱可以隨意改,例如*vals代表非鍵值對的可變數量的引數,**parms代表可變數量的鍵值對引數。使用*args和**kwargs,是一種約定成俗的習慣,你也可以不使用這個名稱。

6、當要同時使用*args和**kwargs時,*args必須寫在**kwargs之前。

7.在函式呼叫中使用”*”,我們需要元組;在函式呼叫中使用”**”,我們需要一個字典