Noah的學習筆記之Python篇:裝飾器


 

Noah的學習筆記之Python篇:

  1.裝飾器

  2.函數“可變長參數”

  3.命令行解析

  

注:本文全原創,作者:Noah Zhang  (http://www.cnblogs.com/noahzn/)

 

  年前工作事務比較繁瑣,我只能用零碎的時間繼續學習Python,決定開一個系列的博文,作為自己深入學習Python的記錄吧。名字也取好了,就叫《ZMAN的學習筆記之Python篇》~開篇是關於裝飾器的,春節假期碼的字哈哈~就讓我們開始吧!

  本文的例子都是自己想的,如果不是很合適,請大家提出寶貴意見哈~謝謝啦!

 

一、為什么要用“裝飾器”

  比如我們寫了如下一段代碼:

# 打印0~99
def func():
    for i in range(100):
        print(i)

 

  我們想要監測執行這個函數花費了多少時間,於是我們將這個函數改成了這樣:

 

import time


# 打印0~99
def func():
    start = time.time()
    for i in range(100):
        print(i)
    end = time.time()
    print("耗時:%.4f" % (end - start))

 

  雖然達到了目的,但是我們卻改變了原有的函數,而且如果有幾十個不同的函數,都這樣改動一下,工作量也是非常大的。

  使用了“裝飾器”后,就能在不修改原函數的前提下,達到相同的功能。

 

二、什么是“裝飾器”

  在Python中,函數是“對象”,而裝飾器是函數,它的作用就是對已經存在的函數進行裝飾。Python中的“裝飾器”可以很好地解決那些有“面向切面編程”需求的問題。

  請看例子:

def deco(ex):
    print('func函數被調用前')
    ex()
    print('func函數被調用后')
    return ex

def func():
    print('func函數被調用')

func = deco(func)


>>> 
func函數被調用前
func函數被調用
func函數被調用后

 

  我寫了兩個函數,將函數func作為參數傳入deco函數,並將返回值賦給func變量。我們可以看成是func函數經過了deco的裝飾~

  而這就是裝飾器的概念了:裝飾器可以說是封裝器,讓我們在被裝飾的函數之前或之后執行一些代碼,而不必修改函數本身。利用這點,我們可以做出許多酷炫的功能~

 

三、寫第一個“裝飾器”

  剛才我介紹了裝飾器的概念,但這是一個“手工”的裝飾器,並沒有用到Python的裝飾器語法,實際上,裝飾器語法非常簡單,看例子:

def deco(ex):
    def _deco():
        print('func函數被調用前')
        ex()
        print('func函數被調用后')
    return _deco


@deco
def func():
    print('func函數被調用')
    #return('OK')


func()


>>> 
func函數被調用前
func函數被調用
func函數被調用后

 

  大家可以看到,我在定義函數func的上一行,加了一句“@deco”,這就是裝飾器語法了,這樣寫了之后,能確保每次調用func函數都被deco函數裝飾,是不是非常簡單呀~~

 

四、讓被裝飾函數帶上確定的參數

  如果被裝飾函數帶可以確定的參數,需要像下面這樣對裝飾器函數進行修改:

def deco(ex):
    def _deco(a, b):
        print('%s函數被調用前' % ex.__name__)
        c = ex(a, b)
        print('%s函數被調用后,結果為:%s' % (ex.__name__, c))
        
    return _deco


@deco
def func(a, b):
    print('func函數被調用,傳入%s,%s' % (a, b))
    return a+b


func(1, 2)


>>> 
func函數被調用前
func函數被調用,傳入1,2
func函數被調用后,結果為:3

 

  這個例子的裝飾器實現了:打印傳入函數的名字、打印兩個數字相加結果的功能。我們在原先的deco函數內又定義了一個函數_deco用來接收func函數中的參數。

 

五、讓被裝飾函數帶上不確定的參數

def deco(ex):
    def _deco(*args, **kwargs):
        print('%s函數被調用前' % ex.__name__)
        c = ex(*args, **kwargs)
        print('%s函數被調用后,結果為%s' % (ex.__name__, c))

    return _deco


@deco
def func(a, b):
    print('func函數被調用,傳入%s,%s' % (a, b))
    return a+b


@deco
def func2(a, b, c):
    print('func2函數被調用,傳入%s,%s,%s' % (a, b, c))
    return a+b+c              


func(1, 2)
func2(1, 2, 3)


>>> 
func函數被調用前
func函數被調用,傳入1,2
func函數被調用后,結果為3
func2函數被調用前
func2函數被調用,傳入1,2,3
func2函數被調用后,結果為6

 

  簡單修改我們的代碼,使用*args, **kwargs來捕捉不定量的傳參,便實現了多個參數的求和。

 

六、讓裝飾器帶上參數

def deco(ex):
    def _deco(func):
        def _deco2():
            print('%s函數被調用前,傳入參數為:%s' % (func.__name__, ex))
            func()
            print('%s函數被調用后' % func.__name__)
        return _deco2
    return _deco

 
@deco('parameter1')
def func():
    print('func函數被調用')

 
func()


>>> 
func函數被調用前,傳入參數為:parameter1
func函數被調用
func函數被調用后

 

  如果要讓裝飾器帶上參數,我們要在裝飾器函數內部再多定義一層函數,用來接收裝飾器的參數~大家可不要搞混了裝飾器參數和函數的參數喲~

 

七、來個任性的:裝飾器和被裝飾函數都帶參數

def deco(ex):
    def _deco(func):
        def _deco2(c, d):
            print('%s函數被調用前,裝飾器參數為:%s' % (func.__name__, ex))
            x = func(c, d)
            if x > 3:
                x = x-ex
            else:
                x = x+ex
            print('%s函數被調用后,計算結果為:%d\n' % (func.__name__, x))
        return _deco2
    return _deco

 
@deco(3)
def func(a, b):
    print('func函數執行結果為:%s' % int(a+b))
    return(a+b)

 
func(3, 4)
func(1, 2)


>>> 
func函數被調用前,裝飾器參數為:3
func函數執行結果為:7
func函數被調用后,計算結果為:4

func函數被調用前,裝飾器參數為:3
func函數執行結果為:3
func函數被調用后,計算結果為:6

 

  最初的func函數只是實現兩個數字的相加,經過裝飾后實現了對func返回的和的大小進行了分支處理:如果“兩數的和大於3”,最后結果為“兩數的和減去3”,否則最后結果為“兩數的和加上3”。我在這個例子中使用的是“確定”的參數,大家可以自己更改哦~

 

八、同時使用多個裝飾器

  之前的例子都是只用了一個裝飾器,我們當然可以裝飾多次啦~

def deco1(ex):
    def _deco1(string):
        print('deco1被調用前')
        string = ex(string)
        if 'hello' in string:
            string = "You are my old friend."
        else:
            string = "You are my new friend."
        print('deco1被調用后,%s\n' % string)

        return string
            
    return _deco1


def deco2(ex):
    def _deco2(string):
        print('deco2被調用前')
        string = ex(string)
        if 'ZMAN' in string:
            string = 'hello, ' + string
        else:
            string = 'Is your name ' + string + '?'
        print('deco2被調用后,%s' % string)

        return string

    return _deco2

@deco1
@deco2
def func(string):
    print('func函數被調用')
    return string

func('ZMAN')
deco1(deco2(func('John')))


>>> 
deco1被調用前
deco2被調用前
func函數被調用
deco2被調用后,hello, ZMAN
deco1被調用后,You are my old friend.

deco1被調用前
deco2被調用前
func函數被調用
deco2被調用后,Is your name John?
deco1被調用后,You are my new friend.

 

  在這個例子中,我們主要要關注裝飾器調用的先后順序,此時func('ZMAN')和deco1(deco2(func('ZMAN')))是等同的,這個調用順序大家一看就明白了吧~

 

九、實際應用

  最后來個實際應用~好吧,我實在是絞盡腦汁了,寫代碼的時候正好在吃蘋果,那就來個跟水果有關的實例吧(別打我 - -!)

#綜合運用:簡單地檢測函數傳參是否合法

def deco(ex):
    def _deco(*args, **kwargs):
        print('***%s函數被調用前***' % ex.__name__)
        if args:
           if not isinstance(args[0], str):
               print('★店名參數錯誤:%s' % args[0])
           if not(isinstance(args[1], int) and args[1]>0):
               print('★員工參數錯誤:%s' % args[1])
               
        else:
            print('★未傳入店名和員工數信息!')

        if kwargs:
            for i in kwargs:
                if not ((isinstance(kwargs[i], int)
                    or isinstance(kwargs[i], float))
                    and kwargs[i]>0):
                    print('★水果單價參數錯誤:%s:%r' % (i, kwargs[i]))
        else:
            print('★未傳入水果單價信息!')

        a = ex(*args, **kwargs)
        print('***%s函數被調用后***\n' % ex.__name__)

    return _deco


@deco
# 假設傳入幾家水果店的名稱、員工數以及水果單價。店名為字符,員工數為正整數,單價為正數
def func(*args, **kwargs):
    print('***函數func被調用***')
    brief = args
    detail = kwargs
    return(brief, detail)
                  

func('水果之家', -4, apple=3.5, strawberry=6, orange=3, cherry=8.5,)
func(123, 8, apple=3, orange=2,)
func('天然果園', 0.2, )
func()


>>> 
***func函數被調用前***
★員工參數錯誤:-4
***函數func被調用***
***func函數被調用后***

***func函數被調用前***
★店名參數錯誤:123
***函數func被調用***
***func函數被調用后***

***func函數被調用前***
★員工參數錯誤:0.2
★未傳入水果單價信息!
***函數func被調用***
***func函數被調用后***

***func函數被調用前***
★未傳入店名和員工數信息!
★未傳入水果單價信息!
***函數func被調用***
***func函數被調用后***

 

  代碼有點長,但是只要大家耐心看,其實還是挺簡單的,沒有什么花里胡哨的東西。這個裝飾器用來檢測傳參是否合法~

 

十、小結

  第一篇洋洋灑灑那么多個例子,終於寫完了!利用“裝飾器”,我們無須改寫原函數,就能對它進行功能擴充,比如計時、檢測傳參、記錄日志等等。就比如我們有一把槍,我們可以給它加上消音器,又或者是刺刀…不用的時候就拿掉,還是原來的槍~~

  (本文難免有寫錯或不足的地方,希望大家不吝賜教哦~謝謝!)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM