python3裝飾器的高階使用
阿新 • • 發佈:2019-02-08
#practice26:快取與裝飾器(遞迴子問題)
對於需要重複計運算元問題的情況,可以使用快取,快取實現有兩種方式:1.在函式內定義某種資料結構儲存資料 2.使用裝飾器(閉包結構)
菲波那切數列為:1,1,2,3,5,8,13;即從第三項開始,每一項為前兩項之和。
以菲波那切數列為例
1、一般的實現方式為:
#求第n項的斐波那契數,從0開始
def fibonacci(n):
if n <= 1:
return 1
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(33))
上述函式中由於需要重複計算某些項,所以計算速度非常緩慢,如果在計算過程中能儲存一些中間值,速度提升非常明顯
2、使用快取:儲存中間值
#方法一:快取法
#cache以值None層層傳遞到最底層,然後建立空字典
#cache逐層傳遞引用,每層的變數cache均指向同一物件!
def fibonacci(n,cache=None):
if cache is None:
cache = {}
if n in cache.keys():
return cache[n]
if n <= 1:
return 1
cache[n] = fibonacci(n-1,cache) + fibonacci(n-2,cache)
return cache[n]
print(fibonacci(33))
這種儲存中間值的方法不太直觀
3、 使用裝飾器,利用閉包儲存中間值
#方法二:使用裝飾器,一來不改變函式形式,二可利用閉包儲存變數狀態,比快取法容易理解
def decorator(f):
cache = {}
def wrapper(*args,**kargs):
if args not in cache.keys():
cache[args] = f(*args,**kargs)
return cache[args]
return wrapper
@decorator
def fibonacci(n):
if n <= 1:
return 1
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(33))
裝飾器中閉包的典型使用場景
#practice27:函式元資料與裝飾器
1、 函式元資料
def f(key,a=1,b=[]):
'''function f'''
print(b)
print(key*2)
#函式名稱
print(f.__name__)
#函式註釋
print(f.__doc__)
#函式所屬模組
print(f.__module__)
#函式的預設引數元組
print(f.__defaults__)
#修改函式引數的預設引數居然成功了,因為元組內包含列表,列表是可變物件!
#正因如此,不應該把可變物件作為引數的預設值!
f.__defaults__[1].append('100')
f(1)
##會報錯,元組的元素不可被賦值
#f.__defaults__[0] = 10
#f(1)
def func1():
a = 3
return lambda x: a*x
g = func1()
#讀取函式的閉包
#每個閉包包含多個cell物件
print(g.__closure__)
#cell物件的cell_contents屬性可以讀取值
print(g.__closure__[0].cell_contents)
2、 使用裝飾器後函式如何保留元資料?
- 被裝飾後的函式,其元資料改為了wrapper函式!
def deco(func):
def wrapper(*args,**kargs):
'''function wrapper'''
print("in wrapper")
func(*args,**kargs)
return wrapper
def f1(n):
'''function f1'''
print(n**2)
print(f1.__name__,f1.__doc__)
@deco
def f2(n):
'''function f2'''
print(n**2)
print(f2.__name__,f2.__doc__)
- 手動修改元資料
def deco(func):
def wrapper(*args,**kargs):
'''function wrapper'''
print("in wrapper")
func(*args,**kargs)
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
def f1(n):
'''function f1'''
print(n**2)
print(f1.__name__,f1.__doc__)
@deco
def f2(n):
'''function f2'''
print(n**2)
print(f2.__name__,f2.__doc__)
- 使用functools模組中的update_wrapper函式與常量
from functools import update_wrapper,WRAPPER_ASSIGNMENTS,WRAPPER_UPDATES
def deco(func):
def wrapper(*args,**kargs):
'''function wrapper'''
print("in wrapper")
func(*args,**kargs)
#使用函式update_wrapper更改被裝飾後的函式wrapper的元資料
#引數1:裝飾後的函式;引數2:被裝飾函式;引數3:指定替換哪些元資料;引數4:指定合併哪些元資料
update_wrapper(wrapper,func,WRAPPER_ASSIGNMENTS,WRAPPER_UPDATES)
return wrapper
def f1(n):
'''function f1'''
print(n**2)
print(f1.__name__,f1.__doc__)
@deco
def f2(n):
'''function f2'''
print(n**2)
print(f2.__name__,f2.__doc__)
#這兩個常量皆為元組,一般情況下就使用這兩個常量作為update_wrapper的實參
print(WRAPPER_ASSIGNMENTS,WRAPPER_UPDATES)
- 使用functools中的wraps裝飾器(帶引數)
from functools import wraps
def deco(func):
#帶引數裝飾器,指定被裝飾的函式,替換的元資料任然為WRAPPER_ASSIGNMENTS,WRAPPER_UPDATES
#效果同上一種方法,不過更簡潔
@wraps(func)
def wrapper(*args,**kargs):
'''function wrapper'''
print("in wrapper")
func(*args,**kargs)
return wrapper
def f1(n):
'''function f1'''
print(n**2)
print(f1.__name__,f1.__doc__)
@deco
def f2(n):
'''function f2'''
print(n**2)
print(f2.__name__,f2.__doc__)
此為推薦方法
#practice28:帶引數裝飾器
1、示例:實現函式引數型別檢查
def type(*types,**ktypes):
def decorator(func):
def wrapper(*args,**kargs):
allmatch = True
for value,_type in zip(args,types):
if not isinstance(value,_type):
print("value: " + str(value) + " is not " + str(_type))
allmatch = False
break
if allmatch:
func(*args,**kargs)
return wrapper
return decorator
@type(int,str,int)
def f1(a,b,c):
print(a,b,c)
@type(int,int,str,tuple)
def f2(a,b,c,d):
print(a,b,c,d)
f1("s",'qq',55)
f2(1,2,3,4)
f2(1,2,"qq",(22,33))
裝飾器的引數最終還是要使用在wrapper內!
2、示例:屬性可修改的函式裝飾器
- 實現函式執行時間檢查
import time
from random import randint
import logging
def runtime(shreshold):
def decorator(func):
def wrapper(*args,**kargs):
start = time.time()
func(*args,**kargs)
end = time.time()
period = end - start
if period > shreshold:
msg = '%s:%s > %s' % (func.__name__,period,shreshold)
logging.warn(msg)
return wrapper
return decorator
@runtime(1.5)
def f1():
print("in test")
while randint(0,1):
time.sleep(5)
for _ in range(30):
f1()
- 使裝飾器屬性可修改
import time
from random import randint
import logging
def runtime(shreshold):
def decorator(func):
def wrapper(*args,**kargs):
start = time.time()
func(*args,**kargs)
end = time.time()
period = end - start
if period > shreshold:
msg = '%s:%s > %s' % (func.__name__,period,shreshold)
logging.warn(msg)
def setTimeout(new_shreshold):
#nonlocal會一直向上查詢
nonlocal shreshold
shreshold = new_shreshold
#定義裝飾器(本質是函式)的可變屬性,該屬性為一個函式
wrapper.setTimeout1 = setTimeout
return wrapper
return decorator
@runtime(1.5)
def f1():
print("in test")
while randint(0,1):
time.sleep(1)
if __name__ == "__main__":
for _ in range(10):
f1()
#經過裝飾器裝飾後的函式f1本質上已經成為wrapper;所以下面是在呼叫f1的屬性罷了
f1.setTimeout1(2.5)
for _ in range(10):
f1()
溫習一下函式作為一等物件如何定義屬性!