Noah的學習筆記之Python篇:
注:本文全原創,作者: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函數被調用后***
代碼有點長,但是只要大家耐心看,其實還是挺簡單的,沒有什么花里胡哨的東西。這個裝飾器用來檢測傳參是否合法~
十、小結
第一篇洋洋灑灑那么多個例子,終於寫完了!利用“裝飾器”,我們無須改寫原函數,就能對它進行功能擴充,比如計時、檢測傳參、記錄日志等等。就比如我們有一把槍,我們可以給它加上消音器,又或者是刺刀…不用的時候就拿掉,還是原來的槍~~
(本文難免有寫錯或不足的地方,希望大家不吝賜教哦~謝謝!)
