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

    通常使用緩存裝飾器,來包裹這些檢查函數,避免其被反復調用,進而提高程序運行效率,比如寫成下面這樣

    @lru_cache
    def check(param1, param2, ...) # 檢查用戶設備類型,版本號等等
        ...
    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM