1. 程式人生 > 實用技巧 >python函式裝飾器和類裝飾器

python函式裝飾器和類裝飾器

目錄

函式裝飾器

1、簡單裝飾器

def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    return wrapper

def greet():
    print('hello world')

greet = my_decorator(greet)
greet()

# 輸出
# wrapper of decorator
# hello world

上述程式碼在 Python 中有更簡單、更優雅的表示:

def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    return wrapper

@my_decorator
def greet():
    print('hello world')

greet()

# 輸出
# wrapper of decorator
# hello world

2、帶引數的裝飾器

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print('wrapper of decorator')
        func(*args, **kwargs)
    return wrapper

@my_decorator
def greet(message):
    print(message)

greet('hello world')

# 輸出
# wrapper of decorator
# hello world

3、自定義引數的裝飾器

def repeat(num):
    def my_decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(num):
                print('wrapper of decorator {}'.format(i))
                func(*args, **kwargs)
        return wrapper
    return my_decorator

@repeat(4)
def greet(message):
    print(message)

greet('hello world')

# 輸出:
# wrapper of decorator 0
# hello world
# wrapper of decorator 1
# hello world
# wrapper of decorator 2
# hello world
# wrapper of decorator 3
# hello world

4、原函式還是原函式嗎
試著打印出 greet() 函式的一些元資訊:

greet.__name__
## 輸出
'wrapper'

help(greet)
# 輸出
Help on function wrapper in module __main__:

wrapper(*args, **kwargs)

greet() 函式被裝飾以後,它的元資訊變了。元資訊告訴我們“它不再是以前的那個 greet() 函式,而是被 wrapper() 函式取代了”。

為了解決這個問題,通常使用內建的裝飾器@functools.wrap,它會幫助保留原函式的元資訊(也就是將原函式的元資訊,拷貝到對應的裝飾器函式裡)。

import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('wrapper of decorator')
        func(*args, **kwargs)
    return wrapper
    
@my_decorator
def greet(message):
    print(message)

greet.__name__

# 輸出
'greet'

類裝飾器

實際上,類也可以作為裝飾器。類裝飾器主要依賴於函式__call__(),每當你呼叫一個類的示例時,函式__call__()就會被執行一次。

class Count:
    def __init__(self, func):
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print('num of calls is: {}'.format(self.num_calls))
        return self.func(*args, **kwargs)

@Count
def example():
    print("hello world")

example()

# 輸出
num of calls is: 1
hello world

example()

# 輸出
num of calls is: 2
hello world

我們定義了類 Count,初始化時傳入原函式 func(),而__call__()函式表示讓變數 num_calls 自增 1,然後列印,並且呼叫原函式。因此,在我們第一次呼叫函式 example()時,num_calls 的值是 1,而在第二次呼叫時,它的值變成了 2

裝飾器的應用

身份認證 authenticate
日誌記錄
輸入合理性檢查 validation_check
快取 lru_cache
通常使用快取裝飾器,來包裹這些檢查函式,避免其被反覆呼叫,進而提高程式執行效率,比如寫成下面這樣