2022年10月29日 星期六

[Python] Decorator 裝飾器 (2) - wraps

上篇文章:
[Python] Decorator 裝飾器

在使用 decorator時,會使得原本被包裹的 function 的 attribute資料遺失。

import time

def measuretime(func):

    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print (func.__name__, end - start)
        return result

    return wrapper

@measuretime
def count_down(n):
    '''Counts down from n'''
    while n > 0:
        n -= 1

if __name__ == '__main__':
    print (count_down.__name__)
    print (count_down.__doc__)

Output:

wrapper
None

可使用 functools的 decorator wraps來避免這種情況:

import time
from functools import wraps

def measuretime(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print (func.__name__, end - start)
        return result

    return wrapper

@measuretime
def count_down(n):
    '''Counts down from n'''
    while n > 0:
        n -= 1

if __name__ == '__main__':
    print (count_down.__name__)
    print (count_down.__doc__)

Output:

count_down
Counts down from n

Unwrapping

wraps提供了另一個有用的功能,可以使已經套用過 decorator的 function回復成套用前的
function。透過 __wrapped__這個屬性來使用原本的 function。

import time
from functools import wraps

def measuretime(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print (func.__name__, end - start)
        return result

    return wrapper

@measuretime
def count_down(n):
    '''Counts down from n'''
    print (f'Counts down from {n}')
    while n > 0:
        n -= 1

if __name__ == '__main__':
    count_down(500000)
    org_count_down = count_down.__wrapped__
    org_count_down(500000)

Output:

Counts down from 500000
count_down 0.021966218948364258
Counts down from 500000

2022年10月23日 星期日

[Python] Decorator 裝飾器 (1)

最近發現自己在 Python上的技巧需要提升,雖然以前就知道 Decorator的概念,但我在
實際應用上很少使用,趁此機會來重新理解下。

Decorator簡單來說,是一種 function,並以另一個 function作為輸入,用來擴充輸入
function的功能。

以下面程式碼為例:

def hello_alice(hello_func):
    hello_func("Alice")

def say_hello(name):
    print (f"Hello {name}")

if __name__ == '__main__':
    hello_alice(say_hello)

Output:

Hello Alice

這裡 hello_alice使用 say_hello當作參數傳入, hello_alice就是作為 say_hello
decorator。


Syntactic Sugar

通常 decorator會使用更簡潔的語法糖 "@"包裝,使用上面的範例修改:

def hello_alice(hello_func):
    hello_func("Alice")

@hello_alice
def say_hello(name):
    print (f"Hello {name}")

if __name__ == '__main__':
    say_hello

Output:

Hello Alice

@hello_alice代表將 say_hello當作參數傳入 hello_alice function。


Inner Functions

Python的 function也能夠在另一個 function中被定義,稱為 inner function。inner function可以
讓 decorator的使用更加靈活:

def greet(func):

    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result

    return wrapper

@greet
def say_hello(name):
    print(f"Hello {name}")

if __name__ == '__main__':
    say_hello("Alice")
    say_hello("Bob")

Output:

Hello Alice
Hello Bob

其中 wrapper就是作為 decorator @greet的 inner function。 並且會使用 *args**kwargs來接收參數。


Example - Measure Time

一個 decorator經典的範例是用於測量 function執行所花的時間:

import time

def measuretime(func):

    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print (func.__name__, end - start)
        return result

    return wrapper

@measuretime
def count_down(n):
    while n > 0:
        n -= 1

if __name__ == '__main__':
    count_down(500000)

count_down 0.018976449966430664

使用 decorator @measure的來測量 count_down function所花費的時間。