Python帶參裝飾器的入門套路
在學習帶參裝飾器前我們首先複習一下無參裝飾器的寫法:
def add(x, y):
return x + y
def logger(fn):
def wrapper(*args, **kwargs): # 這裏是形參的傳入,可以傳入類型最終由fn|add決定
print("before the fn/add")
ret = fn(*args, **kwargs) # 這裏是實參結構
print("after the fn/add")
return ret
return wrapper
add = logger(add)
add(4, 5)
# ===========output=============>
before the fn/add
after the fn/add
9
帶參裝飾器練習:裝飾器文檔字符串被改變
import time
import datetime
def logger(fn):
def wrapper(*args, **kwargs):
"""wrapper's document"""
print("before the fn")
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
print("after the fn")
delta = (datetime.datetime.now() - start).total_seconds()
print("Funtion{} took {}s.".format(fn.__name__,delta))
return ret
return wrapper
@logger # equal to: add = logger(add) # add指向了wrapper
def add(x,y):
""" add's doc ~~~"""
time.sleep(1)
return x+y
print(add.__name__, add.__doc__)
# ==output不是add的document,而是wrapper的document=============>
wrapper wrapper's document
如上,使用裝飾器後add的函數名和文檔都被改變了。如何解決呢?
因爲現在訪問add函數,實際上是在執行wrapper函數,所以使用原來定義的add函數的名稱和文檔屬性,覆蓋wrapper的對應屬性就可以了:
import time
import datetime
def logger(fn):
def wrapper(*args, **kwargs):
"""wrapper's document"""
print("before the fn")
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
print("after the fn")
delta = (datetime.datetime.now() - start).total_seconds()
print("Funtion{} took {}s.".format(fn.__name__,delta))
return ret
def copy_properties(src, dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
copy_properties(fn, wrapper)
return wrapper
@logger # equal to: add = logger(add) # add指向了wrapper
def add(x,y):
"""add's doc ~balabala~"""
time.sleep(1)
return x+y
print(add.__name__, "\n", add.__doc__)
# ==output不是add的document,而是wrapper的document=============>
add
add's doc ~balabala~
寫成函數的方式
import time
import datetime
def logger(fn):
def wrapper(*args, **kwargs):
"""wrapper's document"""
print("before the fn")
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
print("after the fn")
delta = (datetime.datetime.now() - start).total_seconds()
print("Funtion{} took {}s.".format(fn.__name__,delta))
return ret
wrapper.__name__ = fn.__name__
wrapper.__doc__ = fn.__doc__
return wrapper
@logger # equal to: add = logger(add) # add指向了wrapper
def add(x,y):
""" add's doc ~~~"""
time.sleep(1)
return x+y
print(add.__name__,"\n", add.__doc__)
# ==output不是add的document,而是wrapper的document=============>
add
add's doc ~~~
BTW:
類似copy_properties這種函數往往是公用的,所以定義成全局的比較合適,在調用時候,使用裝飾器調用。
import time
import datetime
def copy_properties(src):
def _copy(dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return dst #這裏注意return的返回值很重要,如果沒有返回值,
# 如果沒有返回值就是None。那麼 帶參裝飾器裏的wrapper - > copy_propertiest(fn)-> _copy -> copy(dst) - None
#這樣,如果要print(add.__name__)->print(wrapper._name__)..................................print(None.__name__)
return _copy
def logger(fn):
@copy_properties(fn) #wrapper = copy_properties(fn)(wrapper)
def wrapper(*args, **kwargs):
"""wrapper's document"""
print("before the fn")
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
print("after the fn")
delta = (datetime.datetime.now() - start).total_seconds()
print("Funtion{} took {}s.".format(fn.__name__,delta))
return ret
# def copy_properties(src, dst): # 這是帶參裝飾器的原始狀態
# dst.__name__ = src.__name__
# dst.__doc__ = src.__doc__
# copy_properties(fn, wrapper)
return wrapper
@logger # equal to: add = logger(add) # add指向了wrapper
def add(x,y):
"""add's doc ~balabala~"""
time.sleep(1)
return x+y
print(add.__name__, "\n", add.__doc__)
# ==output不是add的document,而是wrapper的document=============>
add
add's doc ~balabala~
這樣,一個帶參裝飾器就完成了。
import time
import datetime
def copy_properties(src):
def _copy(dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
# return dst #這裏注意return的返回值很重要,如果沒有返回值,
# 如果沒有返回值就是None。那麼 帶參裝飾器裏的wrapper - > copy_propertiest(fn)-> _copy -> copy(dst) - None
#這樣,如果要print(add.__name__)->print(wrapper._name__)..................................print(None.__name__)
return _copy
def logger(fn):
@copy_properties(fn) #wrapper = copy_properties(fn)(wrapper)
def wrapper(*args, **kwargs):
"""wrapper's document"""
print("before the fn")
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
print("after the fn")
delta = (datetime.datetime.now() - start).total_seconds()
print("Funtion{} took {}s.".format(fn.__name__,delta))
return ret
# def copy_properties(src, dst): # 這是帶參裝飾器的原始狀態
# dst.__name__ = src.__name__
# dst.__doc__ = src.__doc__
# copy_properties(fn, wrapper)
return wrapper
@logger # equal to: add = logger(add) # add指向了wrapper
def add(x,y):
"""add's doc ~balabala~"""
time.sleep(1)
return x+y
print(add.__name__, "\n", add.__doc__)
# ===============返回值Return因一個返回值的缺失而報錯=============>
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-63-8df557fa2dde> in <module>
38 return x+y
39
---> 40 print(add.__name__, "\n", add.__doc__)
41 # ===============返回值Return因一個返回值的缺失而報錯=============>
AttributeError: 'NoneType' object has no attribute '__name__'
帶參裝飾器的覆蓋問題
將記錄提取到控制檯,或者記錄到日誌裏面。
import time
import datetime
def copy_properties(src):
def _copy(dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return dst #這裏注意return的返回值很重要,如果沒有返回值,
# 如果沒有返回值就是None。那麼 帶參裝飾器裏的wrapper - > copy_propertiest(fn)-> _copy -> copy(dst) - None
#這樣,如果要print(add.__name__)->print(wrapper._name__)..................................print(None.__name__)
# 另外,如果return不是dst而是src,就會導致新add失效
return _copy
def logger(fn):
@copy_properties(fn) #wrapper = copy_properties(fn)(wrapper)
def wrapper(*args, **kwargs):
"""wrapper's document"""
print("before the fn")
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
print("after the fn")
delta = (datetime.datetime.now() - start).total_seconds()
print("Funtion{} took {}s.".format(fn.__name__,delta))
return ret
# def copy_properties(src, dst): # 這是帶參裝飾器的原始狀態
# dst.__name__ = src.__name__
# dst.__doc__ = src.__doc__
# copy_properties(fn, wrapper)
return wrapper
@logger # equal to: add = logger(add) # add指向了wrapper
def add(x,y):
"""add's doc ~balabala~"""
time.sleep(1)
return x+y
print(add.__name__, "\n", add.__doc__)
add(4,5)
# ==output不是add的document,而是wrapper的document=============>
add
add's doc ~balabala~
before the fn
after the fn
Funtionadd took 1.001673s.
9
Python帶參裝飾器的應用
檢查函數執行時間。
import time
import datetime
def copy_properties(src):
def _copy(dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return dst #
return _copy
def logger(fn):
@copy_properties(fn) #wrapper = copy_properties(fn)(wrapper)
def wrapper(*args, **kwargs):
"""wrapper's document"""
print("before the fn")
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
print("after the fn")
delta = (datetime.datetime.now() - start).total_seconds()
if delta > 3:
print("Function {} took {}s. It is SLOW.".format(fn.__name__, delta))
else:
print("Function {} took {}s. The speed is OK.".format(fn.__name__, delta))
print("Funtion{} took {}s.".format(fn.__name__,delta))
return ret
return wrapper
@logger # equal to: add = logger(add) # add指向了wrapper
def add(x,y):
"""add's doc ~balabala~"""
time.sleep(5)
return x+y
print(add.__name__, "\n", add.__doc__)
add(4,5)
# ==output不是add的document,而是wrapper的document=============>
add
add's doc ~balabala~
before the fn
after the fn
Function add took 5.001807s. It is SLOW.
Funtionadd took 5.001807s.
9
我們可以把delta閾值提出作爲一個參數,這樣就可以自定義了:
import time
import datetime
def copy_properties(src):
def _copy(dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return dst #
return _copy
def logger(duration = 3):
def _logger(fn):
@copy_properties(fn) #wrapper = copy_properties(fn)(wrapper)
def wrapper(*args, **kwargs):
"""wrapper's document"""
print("before the fn")
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
print("after the fn")
delta = (datetime.datetime.now() - start).total_seconds()
if delta > duration:
print("Function {} took {}s. It is SLOW.".format(fn.__name__, delta))
else:
print("Function {} took {}s. The speed is OK.".format(fn.__name__, delta))
print("Funtion{} took {}s.".format(fn.__name__,delta))
return ret
return wrapper
return _logger
@logger(6) # equal to: add = logger(add) # add指向了wrapper
def add(x,y):
"""add's doc ~balabala~"""
time.sleep(5)
return x+y
print(add.__name__, "\n", add.__doc__)
add(4,5)
# ==output不是add的document,而是wrapper的document=============>
add
add's doc ~balabala~
before the fn
after the fn
Function add took 5.001199s. The speed is OK.
Funtionadd took 5.001199s.
9
修改爲便於閱讀的三元表達式:
import time
import datetime
def copy_properties(src):
def _copy(dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return dst #
return _copy
def logger(duration = 3):
def _logger(fn):
@copy_properties(fn) #wrapper = copy_properties(fn)(wrapper)
def wrapper(*args, **kwargs):
"""wrapper's document"""
print("before the fn")
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
print("after the fn")
delta = (datetime.datetime.now() - start).total_seconds()
# print("Function {} took {}s. It is .".format(fn.__name__, delta)) if delta > duration else print("Function {} took {}s. The speed is OK.".format(fn.__name__, delta))
print("Function {} took {}s. It is {}.".format(fn.__name__, delta,
"slow"if delta > duration else "fast"))
return ret
return wrapper
return _logger
@logger(6) # equal to: add = logger(add) # add指向了wrapper
def add(x,y):
"""add's doc ~balabala~"""
time.sleep(5)
return x+y
print(add.__name__, "\n", add.__doc__)
add(4,5)
# ==output不是add的document,而是wrapper的document=============>
add
add's doc ~balabala~
before the fn
after the fn
Function add took 5.001629s. It is fast.
9
爲了靈活,我們對超出閾值的信息使用一個函數記錄:
import time
import datetime
def copy_properties(src):
def _copy(dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return dst #
return _copy
def logger(duration = 3, output = lambda name, delta: print("slow.{} took {}s )".format(name, delta))):
def _logger(fn):
@copy_properties(fn) #wrapper = copy_properties(fn)(wrapper)
def wrapper(*args, **kwargs):
"""wrapper's document"""
print("before the fn")
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
print("after the fn")
delta = (datetime.datetime.now() - start).total_seconds()
if delta > duration:
output(fn.__name__, delta) #這裏的output指代的是函數一個函數,
return ret
return wrapper
return _logger
@logger(2) # equal to: add = logger(add) # add指向了wrapper
def add(x,y):
"""add's doc ~balabala~"""
time.sleep(3)
return x+y
print(add.__name__, "\n", add.__doc__)
add(4,5)
# ==output不是add的document,而是wrapper的document=============>
add
add's doc ~balabala~
before the fn
after the fn
slow.add took 3.000753s )
9