一、編寫無參數的decoratorhtml
Python的 decorator 本質上就是一個高階函數,它接收一個函數做爲參數,而後,返回一個新函數。python
使用 decorator 用Python提供的 @ 語法,這樣能夠避免手動編寫 f = decorate(f) 這樣的代碼。ubuntu
def log(f): def fn(x): print 'call ' + f.__name__ + '()...' return f(x) return fn
對於階乘函數,@log工做得很好:ruby
@log def factorial(n): return reduce(lambda x,y: x*y, range(1, n+1)) print factorial(10)
結果:app
call factorial()... 3628800
可是,對於參數不是一個的函數,調用將報錯:函數
@log def add(x, y): return x + y print add(1, 2)
結果:post
Traceback (most recent call last): File "test.py", line 15, in <module> print add(1,2) TypeError: fn() takes exactly 1 argument (2 given)
由於 add() 函數須要傳入兩個參數,可是 @log 寫死了只含一個參數的返回函數。url
要讓 @log 自適應任何參數定義的函數,能夠利用Python的 *args 和 **kw,保證任意個數的參數老是能正常調用:spa
def log(f): def fn(*args, **kw): print 'call ' + f.__name__ + '()...' return f(*args, **kw) return fn
如今,對於任意函數,@log 都能正常工做。.net
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# 編寫一個@performance,它能夠打印出函數調用的時間
import
time
def
performance(f):
def
fn(
*
args,
*
*
kw):
t1
=
time.time()
r
=
f(
*
args,
*
*
kw)
t2
=
time.time()
print
'call %s() in %fs'
%
(f.__name__, (t2
-
t1))
return
r
return
fn
@performance
def
factorial(n):
return
reduce
(
lambda
x,y: x
*
y,
range
(
1
, n
+
1
))
print
factorial(
10
)
|
補充:python中的魔法參數:*args和**kwargs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
def
foo(*args, **kwargs):
print
'args = '
, args
print
'kwargs = '
, kwargs
print
'---------------------------------------'
if
__name__ ==
'__main__'
:
foo(
1
,
2
,
3
,
4
)
foo(a=
1
,b=
2
,c=
3
)
foo(
1
,
2
,
3
,
4
, a=
1
,b=
2
,c=
3
)
foo(
'a'
,
1
, None, a=
1
, b=
'2'
, c=
3
)
輸出結果以下:
args = (
1
,
2
,
3
,
4
)
kwargs = {}
—————————————
args = ()
kwargs = {‘a’:
1
, ‘c’:
3
, ‘b’:
2
}
—————————————
args = (
1
,
2
,
3
,
4
)
kwargs = {‘a’:
1
, ‘c’:
3
, ‘b’:
2
}
—————————————
args = (‘a’,
1
, None)
kwargs = {‘a’:
1
, ‘c’:
3
, ‘b’: ’
2
′}
—————————————
能夠看到,這兩個是python中的可變參數。*args表示任何多個無名參數,它是一個tuple;**kwargs表示關鍵字參數,它是一個 dict。而且同時使用*args和**kwargs時,必須*args參數列要在**kwargs前,像foo(a=
1
, b=’
2
′, c=
3
, a’,
1
, None, )這樣調用的話,會提示語法錯誤「SyntaxError: non-keyword arg after keyword arg」。
還有一個很漂亮的用法,就是建立字典:
def
kw_dict(**kwargs):
return
kwargs
print kw_dict(a=
1
,b=
2
,c=
3
) == {
'a'
:
1
,
'b'
:
2
,
'c'
:
3
}
其實python中就帶有dict類,使用dict(a=
1
,b=
2
,c=
3
)便可建立一個字典了。
|
二、編寫帶參數的decorator
考察上一節的 @log 裝飾器:
def log(f): def fn(x): print 'call ' + f.__name__ + '()...' return f(x) return fn
發現對於被裝飾的函數,log打印的語句是不能變的(除了函數名)。
若是有的函數很是重要,但願打印出'[INFO] call xxx()...',有的函數不過重要,但願打印出'[DEBUG] call xxx()...',這時,log函數自己就須要傳入'INFO'或'DEBUG'這樣的參數,相似這樣:
@log('DEBUG') def my_func(): pass
把上面的定義翻譯成高階函數的調用,就是:
my_func = log('DEBUG')(my_func)
上面的語句看上去仍是比較繞,再展開一下:
log_decorator = log('DEBUG') my_func = log_decorator(my_func)
上面的語句又至關於:
log_decorator = log('DEBUG') @log_decorator def my_func(): pass
因此,帶參數的log函數首先返回一個decorator函數,再讓這個decorator函數接收my_func並返回新函數:
def log(prefix): def log_decorator(f): def wrapper(*args, **kw): print '[%s] %s()...' % (prefix, f.__name__) return f(*args, **kw) return wrapper return log_decorator @log('DEBUG') def test(): pass print test()
執行結果:
[DEBUG] test()... None
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import time
def performance(unit): def perf_decorator(f): def wrapper(*args, **kw): t1 = time.time() r = f(*args, **kw) t2 = time.time() t = (t2 - t1) * 1000 if unit== 'ms' else (t2 - t1) print 'call %s() in %f %s' % (f.__name__, t, unit) return r return wrapper return perf_decorator @performance( 'ms') # factorial = performance('ms')(factorial) def factorial(n): return reduce( lambda x,y: x*y, range( 1, n+ 1)) print factorial( 10) # 總結(代碼不能運行) @performance # factorial = performance(factorial) @performance( 'ms') # factorial = performance('ms')(factorial) def factorial(n): pass |
三、完善decorator
@decorator能夠動態實現函數功能的增長,可是,通過@decorator「改造」後的函數,和原函數相比,除了功能多一點外,有沒有其它不一樣的地方?
在沒有decorator的狀況下,打印函數名:
def f1(x): pass print f1.__name__
輸出: f1
有decorator的狀況下,再打印函數名:
def log(f): def wrapper(*args, **kw): print 'call...' return f(*args, **kw) return wrapper @log def f2(x): pass print f2.__name__
輸出: wrapper
可見,因爲decorator返回的新函數函數名已經不是'f2',而是@log內部定義的'wrapper'。這對於那些依賴函數名的代碼就會失效。decorator還改變了函數的__doc__等其它屬性。若是要讓調用者看不出一個函數通過了@decorator的「改造」,就須要把原函數的一些屬性複製到新函數中:
def log(f): def wrapper(*args, **kw): print 'call...' return f(*args, **kw) wrapper.__name__ = f.__name__ wrapper.__doc__ = f.__doc__ return wrapper
這樣寫decorator很不方便,由於咱們也很難把原函數的全部必要屬性都一個一個複製到新函數上,因此Python內置的functools能夠用來自動化完成這個「複製」的任務:
import functools def log(f): @functools.wraps(f) def wrapper(*args, **kw): print 'call...' return f(*args, **kw) return wrapper
最後須要指出,因爲咱們把原函數簽名改爲了(*args, **kw),所以,沒法得到原函數的原始參數信息。即使咱們採用固定參數來裝飾只有一個參數的函數:
def log(f): @functools.wraps(f) def wrapper(x): print 'call...' return f(x) return wrapper
也可能改變原函數的參數名,由於新函數的參數名始終是 'x',原函數定義的參數名不必定叫 'x'。
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 帶參數的@decorator,@functools.wraps的放置 import time, functools def performance(unit): def perf_decorator(f): @functools.wraps(f) def wrapper(*args, **kw): return f(*args, **kw) return wrapper return perf_decorator @performance( 'ms') def factorial(n): return reduce( lambda x,y: x*y, range( 1, n+ 1)) print factorial.__name__ |
四、偏函數
當一個函數有不少參數時,調用者就須要提供多個參數。若是減小參數個數,就能夠簡化調用者的負擔。
好比,int()函數能夠把字符串轉換爲整數,當僅傳入字符串時,int()函數默認按十進制轉換:
>>> int('12345') 12345
但int()函數還提供額外的base參數,默認值爲10。若是傳入base參數,就能夠作 N 進制的轉換:
>>> int('12345', base=8) 5349 >>> int('12345', 16) 74565
假設要轉換大量的二進制字符串,每次都傳入int(x, base=2)很是麻煩,因而,咱們想到,能夠定義一個int2()的函數,默認把base=2傳進去:
def int2(x, base=2): return int(x, base)
這樣,咱們轉換二進制就很是方便了:
>>> int2('1000000') 64 >>> int2('1010101') 85
functools.partial就是幫助咱們建立一個偏函數的,不須要咱們本身定義int2(),能夠直接使用下面的代碼建立一個新的函數int2:
>>> import functools >>> int2 = functools.partial(int, base=2) >>> int2('1000000') 64 >>> int2('1010101') 85
因此,functools.partial能夠把一個參數多的函數變成一個參數少的新函數,少的參數須要在建立時指定默認值,這樣,新函數調用的難度就下降了。
摘自:慕課網