Python 用裝飾房子來分享 Decorator 裝飾器


建立時間: 2023年3月19日 08:57
更新時間: 2023年3月19日 14:48

說明

起初我是從設計模式(Design Pattern)看到其中一個叫做裝飾者模式(Decorator Pattern),Python 剛好有內建的語法支援 Decorator (後面以裝飾器稱呼),自己偶爾會看到別人寫的程式會用到它,但又常常忘記怎麼用,導致有點看不懂別人在寫什麼,所以我打算寫一篇用 Python 裝飾房子的程式。

最簡單的裝飾器

要裝飾房子之前要先取得一棟房子

def get_house():
    print('我取得了一棟房子')


get_house()
輸出:
我取得了一棟房子

這棟房子裡面有人信耶穌,所以要放一個十字架

def get_house():
    print('我取得了一棟房子')


def put_cross(original_function):
    def wrap():
        original_function()
        print('我放了一個十字架')

    return wrap


decorator = put_cross(get_house)
decorator()

輸出:
我取得了一棟房子
我放了一個十字架

這裡需有一個裝飾器的概念,就是目的要在不影響原本的功能進行擴充,所以將原本 get_house() 包裹在 put_cross() 裡面,就好像房子是禮物,十字架是包裝紙。

如果原本的函式有參數,可以改用下面的範例,取得指定顏色的房子。

def get_house(color):
    print(f'我取得了一棟{color}色的房子')


def put_cross(original_function):
    def wrap(color):
        original_function(color)
        print('我放了一個十字架')

    return wrap


decorator = put_cross(get_house)
decorator('黃')
輸出:
我取得了一棟黃色的房子
我放了一個十字架

語法糖(Syntactic sugar)

語法糖是一種概念,它可以使程式碼更易讀、更容易理解和更簡潔,並不是專指 Python 裝飾器,很多程式都有用到語法糖的概念,下列為裝飾器的語法糖。

def put_cross(original_function):
    def wrap(color):
        original_function(color)
        print('我放了一個十字架')

    return wrap


@put_cross
def get_house(color):
    print(f'我取得了一棟{color}色的房子')


get_house('黃')
輸出:
我取得了一棟黃色的房子
我放了一個十字架

@put_cross 簡化了呼叫裝飾器函式,當你知道原本的程式後,你就會發覺這樣寫更容易理解。

多個裝飾器,其先後順序

這房子裡面還有一個是拜媽祖的,所以要再放一尊媽祖。

def put_mazu(original_function):
    def wrap(color):
        original_function(color)
        print('我放了一尊媽祖')

    return wrap


def put_cross(original_function):
    def wrap(color):
        original_function(color)
        print('我放了一個十字架')

    return wrap


@put_mazu
@put_cross
def get_house(color):
    print(f'我取得了一棟{color}色的房子')


get_house('黃')
輸出:
我取得了一棟黃色的房子
我放了一個十字架
我放了一尊媽祖

從上面的輸出,可以看到原本函式會是這樣呼叫的

decorator = put_mazu(put_cross(get_house))
decorator('黃')

所以裝飾器會從外往內包裹。

裝飾器參數

如果裝飾器要帶參數,只需要在裝飾器外面包裹一個函式作為參數即可。

def put_mazu(original_function):
    def wrap(color):
        original_function(color)
        print('我放了一尊媽祖')

    return wrap


def put_cross(cross_color):
    def wrap_put_cross(original_function):
        def wrap(color):
            original_function(color)
            print(f'我放了一個{cross_color}色的十字架')

        return wrap

    return wrap_put_cross


@put_mazu
@put_cross('白')
def get_house(color):
    print(f'我取得了一棟{color}色的房子')


get_house('黃')
輸出:
我取得了一棟黃色的房子
我放了一個白色的十字架
我放了一尊媽祖

原本的 put_cross 變成 wrap_put_cross,新的 put_cross 新增了一個 cross_color 的參數,然後裝飾器就可以呼叫參數 @put_cross('白')

用類別作為裝飾器

裝飾器類別有帶參數跟沒帶參數的用法不一樣,這個水很深,我沒有深入了解,以下僅提供這兩種範例。

無參數類別裝飾器

class God:
    def __init__(self, original_function):
        self._original_function = original_function

    def __call__(self, color):
        self._original_function(color)
        print('我放了一尊神明在這')


@God
def get_house(color):
    print(f'我取得了一棟{color}色的房子')


get_house('黃')
輸出:
我取得了一棟黃色的房子
我放了一尊神明在這

有參數類別裝飾器

class God:
    def __init__(self, name):
        self._name = name

    def __call__(self, original_function):
        def decorate(*args, **kwargs):
            original_function(*args, **kwargs)
            print(f'我放了一尊{self._name}在這')

        return decorate


@God('媽祖')
def get_house(color):
    print(f'我取得了一棟{color}色的房子')


get_house('黃')
輸出:
我取得了一棟黃色的房子
我放了一尊媽祖在這

結論

本篇只說明了基礎的裝飾器用法,我在網路上還有看到許多裝飾器進階用法,例如使用裝飾器會改變函式名稱,因為實際上呼叫的是已經裝飾的函式,這時就可以利用 from functools import wraps,在裝飾器裡面使用 @wraps 裝飾器,反裝飾回去進而取得原函式名稱,應該是這樣,當然還有很多不錯的用法,大家可以自行研究。

參考

觀看次數: 684
decoratorpython
按讚追蹤 Enjoy 軟體 Facebook 粉絲專頁
每週分享資訊技術

一杯咖啡的力量,勝過千言萬語的感謝。

支持我一杯咖啡,讓我繼續創作優質內容,與您分享更多知識與樂趣!