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