1. 程式人生 > >python3裝飾器的高階使用

python3裝飾器的高階使用

#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()

溫習一下函式作為一等物件如何定義屬性!