裝飾器 python

艾利金德發表於2018-01-06

定義:在不修改一個函式內部程式碼的情況下,給函式新增擴充套件功能,即只能在函式之前或者之後新增功能,不能在函式內部新增,常見的應用場景如log日誌、許可權檢驗等。

1.不帶引數的裝飾器

  1. 無引數無返回值得函式

    # 定義一個裝飾器
    def set_func(func):
        def call_func():
            print('————在函式之前新增功能——————')
            func()
            print('————在函式後面新增功能——————')
        return call_func
    
    # 定義一個函式,並新增裝飾器
    @set_func    # 等價於  func1=set_func(func1)
    def func1():
        print('____func1____')
    
    # 呼叫函式
    func1()複製程式碼

  • 程式碼執行結果
    ————在函式之前新增功能——————
    ____func1____
    ————在函式後面新增功能——————
    
    複製程式碼

2. 有引數有返回值得函式

# 定義裝飾器
def set_func(func):
    def call_func(*args, **kwargs):
        print('——————新增功能————————')
        return func(*args, **kwargs)
    return call_func


# 定義一個函式並進行裝飾
@set_func
def func1(m):
    print('____func1____')
    return m

# 呼叫函式
f = func1(100)
print(f)複製程式碼
  • 程式碼執行結果

——————新增功能————————
____func1____
100複製程式碼
2.帶有引數的裝飾器

import time


# 定義裝飾器
def set_log(log):
    def set_func(func):
        # 定義log_dict 字典
        log_dict = {1: 'error', 2: 'warning'}
        def call_func(*args, **kwargs):
            # 開啟log.txt檔案(沒有就建立檔案),追加內容
            with open('log.txt', 'a', encoding='utf-8') as f:
                f.write('%s ---%s---呼叫了函式%s\n' % (log_dict[log], str(time.ctime()), func.__name__))
            return func(*args, **kwargs)
        return call_func
    return set_func


# 定義一個函式並進行裝飾
@set_log(1)
def func1(m):
    print('____func1____')
    return m

# 呼叫函式
f = func1(100)
print(f)複製程式碼

  • 程式碼執行結果

____func1____
100

# log.txt 檔案內容
error ---Fri Jan  5 22:01:24 2018---呼叫了函式func1複製程式碼

3.一個函式有兩個裝飾器

# 定義裝飾器1
def set_log(func):
    print('————開始裝飾sel_log————')

    def call_func():
        print('___set_log___')
        func()
    return call_func


# 定義裝飾器2
def set_func(func):
    print('————開始裝飾set_func————')

    def call_func():
        print('___set_func___')
        func()
    return call_func


# 定義函式,並新增裝飾器
@set_log
@set_func    # 等價於  func1=set_func(func1)
def func1():
    print('____func1____')複製程式碼

  • 先不呼叫函式執行程式碼,會看到如下結果

    ————開始裝飾set_func————
    ————開始裝飾sel_log————
    複製程式碼

  • 呼叫函式,再看執行結果

    # 呼叫函式
    func1()複製程式碼

# 執行結果

————開始裝飾set_func————
————開始裝飾sel_log————
___set_log___
___set_func___
____func1____複製程式碼
  • 解釋說明:
  1. 裝飾器在程式碼寫完後,此時不用呼叫函式,就會進行裝飾
  2. 函式裝飾時,會先執行離函式最近的裝飾器
  3. 當呼叫函式時,函式會先執行離函式最遠的裝飾器再執行離的近的裝飾器,再執行函式本身

4. 一、functools.wraps

import time
import functools

# 定義裝飾器
def clock(func):
    @functools.wraps(func)
    # 使用 functools.wraps裝飾器把相關的屬性從 func複製到 clocked 中,避免遮蓋了被裝飾函式的 __name__ 和 __doc__ 屬性
    def clocked(*args, **kwargs):
        t0 = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - t0
        # 當前執行的函式名稱
        name = func.__name__
        arg_lst = []
        if args:
            arg_lst.append(', '.join(repr(arg) for arg in args))
        if kwargs:
            pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
            arg_lst.append(', '.join(pairs))
        arg_str = ', '.join(arg_lst)
        print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result))

        return result

    return clocked複製程式碼

二、使用functools.lru_cache做備忘

functools.lru_cache 是非常實用的裝飾器,它實現了備忘(memoization)功能。這是一 項優化技術,它把耗時的函式的結果儲存起來,避免傳入相同的引數時重複計算,表明快取不會無限制增長,一段時間不用的快取 條目會被扔掉。

# 用上一個裝飾器clock
@clock
def fib(n):
    if n < 2:
        return 1
    return fib(n-2) + fib(n-1)


if __name__ == '__main__':
    fib(5)

# 執行結果
[0.00000000s] fib(1) -> 1 
[0.00000000s] fib(0) -> 1 
[0.00000000s] fib(1) -> 1 
[0.00000000s] fib(2) -> 2 
[0.00000000s] fib(3) -> 3 
[0.00000000s] fib(0) -> 1 
[0.00000000s] fib(1) -> 1 
[0.00000000s] fib(2) -> 2 
[0.00000000s] fib(1) -> 1 
[0.00000000s] fib(0) -> 1 
[0.00000000s] fib(1) -> 1 
[0.00000000s] fib(2) -> 2 
[0.00000000s] fib(3) -> 3 
[0.00000000s] fib(4) -> 5 
[0.00000000s] fib(5) -> 8 

Process finished with exit code 0

複製程式碼

# 用functools.lru_cache進行裝飾
@functools.lru_cache()
@clock
def fib(n):
    if n < 2:
        return 1
    return fib(n-2) + fib(n-1)if __name__ == '__main__':
    fib(5)# 執行結果
[0.00000000s] fib(1) -> 1 
[0.00000000s] fib(0) -> 1 
[0.00000000s] fib(2) -> 2 
[0.00100088s] fib(3) -> 3 
[0.00000000s] fib(4) -> 5 
[0.00100088s] fib(5) -> 8 
複製程式碼

三、引數選擇functools.lru_cache(maxsize=128, typed=False)

maxsize 引數指定儲存多少個呼叫的結果。快取滿了之後,舊的結果會被扔掉,騰出空間。 為了得到最佳效能,maxsize 應該設為 2 的冪。typed 引數如果設為 True,把不同引數型別 得到的結果分開儲存,即把通常認為相等的浮點數和整數引數(如 1 和 1.0)區分開。順 便說一下,因為 lru_cache 使用字典儲存結果,而且鍵根據呼叫時傳入的定位引數和關鍵 字引數建立,所以被 lru_cache 裝飾的函式,它的所有引數都必須是可雜湊的。

四、單分派泛函式singledispatch

可以把整體方案拆成多個模組,甚至可以為你無法修改的類提供專門的函式,使用@singledispatch裝飾的函式會變成泛函式

  • 1、singledispatch:標記處理object型別的基函式 
  • 2、各個專門函式使用@<<base_function>>.register(<<type>>)裝飾 
  • 3、專門函式的名稱無關緊要,_是個不錯的選擇,簡單明瞭 
  • 4、為每個需要處理的型別註冊一個函式 
  • 5、可以疊放多個register裝飾器,讓同一個函式支援不同型別

from functools import singledispatch

@singledispatch
def show(obj):
    print(obj, type(obj), "obj")


# 引數字串
@show.register(str)
def _(text):
    print(text, type(text), "str")


# 引數int
@show.register(int)
def _(n):
    print(n, type(n), "int")


# 引數元祖或者字典均可
@show.register(tuple)
@show.register(dict)
def _(tup_dic):
    print(tup_dic, type(tup_dic), "int")


if __name__ == '__main__':
    show(1)
    show("xx")
    show([1])
    show((1, 2, 3))
    show({"a": "b"})

# 執行結果
1 <class 'int'> int
xx <class 'str'> str
[1] <class 'list'> obj
(1, 2, 3) <class 'tuple'> int
{'a': 'b'} <class 'dict'> int
複製程式碼


相關文章