04 Python 基礎教學10 所有文章

[Python教學] 裝飾詞原理到應用

Python 裝飾詞 (Decorator) 被大量廣泛的使用在各方 library,是非常實用和必須了解的基礎,本篇介紹了從原理到應用,建議讀者可以跟著敲一次 code,會對裝飾詞有更深入的了解唷~

▍Python 裝飾詞的優點:

  1. 降低程式碼重複率
  2. 易讀性高
  3. 靈活度高

▍本篇的大綱如下:

  1. 裝飾詞原理
  2. 語法糖 (Syntax Candy)
  3. 副作用:函式名稱
  4. 裝飾詞觸發先後順序
  5. 帶參數 (*args, **kargs)
  6. 用 Class 寫裝飾詞

一. 裝飾詞的原理

▍首先了解:變數名 與 函式物件 (object)

我們先定義了一個函式為 foo,我們 print(foo),會得到 function foo at 0x1028831e0 表示 foo 是一個變數名,並且指向一個函式物件 (object) 。
如果要調用函式的話,則加上(), foo(),即可調用函式。

def foo():
    return ‘bar’

print(foo)
print(foo())

>>> <function foo at 0x1028831e0>
>>> bar

▍開始解說裝飾詞:

首先我們在 def timer(func): 將 func 變數名稱傳入,接下來定義 def wrap(sleep_time) 函式,並且在裡面將剛剛傳入的 func(sleep_time) 調用,這樣就完成一個簡單的裝飾詞囉!

import time

def timer(func):
    def wrap(sleep_time):
        t_start = time.time()
        func(sleep_time)
        t_end = time.time()
        t_count = t_end - t_start
        print('[花費時間]', t_count)

    return wrap


def dosomething(sleep_time):
    print('do some thing')
    time.sleep(sleep_time)


foo = timer(dosomething)
foo(3)

>>> do some thing
>>> [花費時間] 3.004279136657715

二. 語法糖 (Syntax Candy)

如果不想寫成 foo = timer(dosomething),只需要加上 @timer,並直接調用 dosomething() 函式執行


def timer(func):
    def wrap(sleep_time):
        t_start = time.time()
        func(sleep_time)
        t_end = time.time()
        t_count = t_end - t_start
        print(‘[花費時間]’, t_count)

    return wrap

@timer
def dosomething(sleep_time):
    print(‘do some thing’)
    time.sleep(sleep_time)

dosomething(3)

>>> do some thing
>>> [花費時間] 3.003619909286499

三. 裝飾詞的副作用:函式名稱

裝飾詞在被 wrap 包一層後,其 __name__ 屬性就會被修改成 wrap

def timer(func):
    def wrap():
        t_start = time.time()
        func()
        t_end = time.time()
        t_count = t_end - t_start
        print(‘[花費時間]’, t_count)

    return wrap


@timer
def dosomething():
    print('do some thing')


dosomething()

print(dosomething.__name__)
>>> wrap

如果要消除這個副作用的話,可以使用 python 內建的 functools,只需要在 def wrap()之前,加上 @wraps(func),即可獲得原先的 __name__ 屬性 dosomething。

from functools import wraps

def timer(func):
    @wraps(func)
    def wrap():
        t_start = time.time()
        func()
        t_end = time.time()
        t_count = t_end - t_start
        print(‘[花費時間]’, t_count)

    return wrap

@timer
def dosomething():
    print('do some thing')


dosomething()

print(dosomething.__name__)
>>> dosomething

四. 觸發先後順序

如果有兩個 Decorator 裝飾詞要使用的話怎麼辦,其實只需要加在上面一行即可,順序的話會從上而下觸發

def timer(func):
    @wraps(func)
    def wrap():
        t_start = time.time()
        func()
        t_end = time.time()
        t_count = t_end - t_start
        print(‘[花費時間]’, t_count)

    return wrap


def func_print_one(func):
    @wraps(func)
    def wrap():
        print(‘this is func_print_one’)
        func()

    return wrap


def func_print_two(func):
    @wraps(func)
    def wrap():
        print('this is func_print_two')
        func()

    return wrap


@timer
@func_print_one
@func_print_two
def dosomething():
    print('do some thing')


dosomething()

>>> this is func_print_one
>>> this is func_print_two
>>> do some thing
>>> [花費時間] 2.3126602172851562e-05

五. 帶參數 (*args, **kargs)

只需在 def wrap() 和 func() 中加入 *args, **kargs 即可調用參數,範例如下:

def timer(func):
    @wraps(func)
    def wrap(*args, **kargs):
        t_start = time.time()
        value = func(*args, **kargs)
        t_end = time.time()
        t_count = t_end - t_start
        print('[花費時間]', t_count)
        return value
    return wrap

def func_print(func):
    @wraps(func)
    def wrap(*args, **kargs):
        print('this is func_print')
        value = func(*args, **kargs)
        return value

    return wrap

@timer
@func_print
def dosomething(a, b):
    print('do some thing')
    print(a + b)


dosomething(1, 2)

>>> this is func_print
>>> do some thing
>>> 3
>>> [花費時間] 1.52587890625e-05

六. 用 Class 寫裝飾詞

如果是 Class 的方法來寫裝飾詞的話,會將 wrap 寫在 __call__ 裡面來調用,範例如下:

class Timer:
    def __init__(self, time_sleep):
        print('[__init__]')
        print('[time_sleep]:', time_sleep)

        self.time_sleep = time_sleep

    def __call__(self, func):
        @wraps(func)
        def wrap(*args, **kargs):
            t_start = time.time()
            time.sleep(self.time_sleep)
            value = func(*args, **kargs)
            t_end = time.time()
            t_count = t_end - t_start
            print('[共花費時間]', t_count)
            return value

        return wrap


@Timer(time_sleep=3)
def dosomethingClass(a, b):
    print('do some thing')
    print('a + b = ', a + b)


dosomethingClass(1, 2)

>>> [__init__]
>>> [time_sleep]: 3
>>> do some thing
>>> a + b =  3
>>> [共花費時間] 3.0002880096435547

本篇簡單的介紹了裝飾詞的原理、語法糖的使用、函式名稱的副作用、觸發的先後順序、帶參數和如何使用 Class來寫裝飾詞,建議可以實際將 code 打一次,會對裝飾詞有更深的了解唷~

Python相關教學延伸閱讀:

那麼 [Python教學] 裝飾詞原理到應用 結束囉,感謝收看!

有關 Max行銷誌 的最新文章,都會發佈在 Max行銷誌的 Facebook 粉絲專頁,如果想看最新更新,還請您按讚或是追蹤唷!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *