文中知識點和代碼示例學習自慕課網,python進階部分(http://www.imooc.com/learn/317) .學習筆記
裝飾器的理解:
裝飾器本質上是一個高階函數,接受一個函數,進行處理,然后返回一個新的函數。
# 有一個簡單的函數
def f1(x):
return x * 2
print f1(5)
#輸出:10
# 實現運行該函數時,輸出該函數的名稱的日志功能
# 法一:直接在函數中寫出,好理解,但是如果函數很多,那每個函數都要加一遍
def f1(x):
print "call " + f1.__name__ + "().."
return x*2
print f1(5)
#輸出:
#call f1()..
#10
# 法二:使用裝飾器,創建裝飾器函數
def flog(f): #定義裝飾器函數,接受參數是 f 函數
def fn(x): #定義新的函數,來處理 f 函數,添加我們需要的日志信息,并返回 f 函數,參數 x 是 f1 的參數,如果 f1 函數有多個,這邊也要寫多個,要讓 @log 自適應任何參數定義的函數,可以利用Python的 *args 和 **kw,保證任意個數的參數總是能正常調用
print "call " + f.__name__ + "().."
return f(x) #執行原 f 函數
return fn #返回新的函數
# 調用裝飾器,效果和 g1 = flog(f1);print g1(5) 一致。
@flog
def f1(x):
return x * 2
print f1(5)
#輸出:
#call f1()..
#10
注: 上邊f1(x)
只接受一個函數,如果接受兩個函數就會報錯,要讓 @flog
自適應任何參數定義的函數,可以利用Python的 *args
和 **kw
,保證任意個數的參數總是能正常調用
例:計算函數調用的時間可以記錄調用前后的當前時間戳,然后計算兩個時間戳的差。
import time
# 定義裝飾器
def performance(f):
def fn(*args, **kw): #可以接受任意參數
t1 = time.time() #記錄執行前的時間
r = f(*args, **kw) #執行原函數,并保存執行結果到變量 r 中
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)
帶參數的裝飾器
上邊的實現的裝飾器函數,只能輸出固定的內容,除了f.__name__
所定義的函數名稱。如果想要根據函數的不同來給輸出的日志劃分等級的,如 a 函數日志等級為info
,b 函數日志等級為debug
,這里需要用到帶參數的裝飾器。如:
@log('DEBUG')
def my_func():
pass
@log('DEBUG')
等于之前無參數的裝飾器的@log
,即@log('DEBUG')
這個返回的函數相當于之前無參數裝飾器的@log
。所以要在無參數的裝飾器上層再加一個函數的嵌套。
例:輸出日志等級
#coding=utf-8
"""
帶參數的裝飾器,寫三層嵌套的函數
"""
def flog(devel): # 定義帶參數的裝飾器.
def log_decorator(f): #和無參數的裝飾器相同
def add_self(*args,**kwargs): #添加輸出的日志,執行f函數
print "[%s],call %s().." % (devel,f.__name__)
return f(*args,**kwargs)
return add_self
return log_decorator #返回給flog("INFO")
@flog("INFO")
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))
print factorial(10)
"""
輸出
[INFO],call factorial()..
3628800
"""
例2:上一節的@performance
只能打印秒,請給@performace
增加一個參數,允許傳入's'
或'ms'
:
import time
def performance(unit):s
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')
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))
print factorial(10)
完善裝飾器
經過@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
的“改造”,就需要把原函數的一些屬性復制到新函數中。
Python內置的functools
可以用來自動化完成這個“復制”的任務:
import functools
def log(f):
@functools.wraps(f) #增加該行
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
return wrapper
最后需要指出,由于我們把原函數簽名改成了(*args, **kw)
,因此,無法獲得原函數的原始參數信息。即便我們采用固定參數來裝飾只有一個參數的函數
例:該方法使用到上節的例子中
import time, functools
def performance(unit):
def perf_decorator(f):
@functools.wraps(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')
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))
print factorial.__name__