Python 的裝飾符
上回我發了一篇文章談到 functools 模組, 這回就來談談 Python 的裝飾符 ‘@’.
裝飾函式
這裡我們先說明裝飾函式, 針對某些函式我們想要在呼叫前和呼叫後作一些動作, 就必須加寫一些語句來處理. 比如我們想要記錄一個函式被呼叫的時間以及函式執行所發的時間.
>>> from datetime import datetime
>>>
>>> def circle_area(radius):
>>> area = 3.14*radius**2
>>> print(f'Area of circle with radius {radius} is {area}')
>>> return area
>>>
>>> now = datetime.now()
>>> print(f'func called at {now}')
>>> circle_area(3)
>>> print(f'Execution time is {datetime.now()-now}')
func called at 2020-06-11 20:33:37.828129
Area of circle with radius 3 is 28.26
Execution time is 0:00:00.007016
當然這樣可以達到我們要的結果, 但是如果有別的函式也要作同樣的事呢? 那不是又要增加那些程式碼? 函式是用來處理這樣問題. 因此我們就要寫一個函式專門作這事.
>>> from datetime import datetime
>>>
>>> def record_time(func, *args, **kwargs):
>>> now = datetime.now()
>>> print(f'func called at {now}')
>>> value = func(*args, **kwargs)
>>> print(f'Execution time is {datetime.now()-now}')
>>> return value
>>>
>>> def circle_area(radius):
>>> area = 3.14*radius**2
>>> print(f'Area of circle with radius {radius} is {area}')
>>> return area
>>>
>>> record_time(circle_area, 3)
func called at 2020-06-11 20:40:32.256889
Area of circle with radius 3 is 28.26
Execution time is 0:00:00.008006
這樣就可以在各個需要的函式來呼叫 record_time, 這個 record_time 就可以稱作裝飾函式.
裝飾符
使用裝飾函式, 還是有點複雜, 因為呼叫各個函式時, 都得加上裝飾函式, 還要分割引數. 因此在 Python 中就有一個所謂的裝飾符 ‘@’, 可以來幫我們簡化這件事. 基本上, 就是建立一個新的函式, 來取代你想用的函式, 其中新的函式前後包含你想要作的事.
from datetime import datetime
def record_time(func):
""" 建立一個新的函式 """
def new_function(*args, **kwargs):
now = datetime.now()
print(f'func called at {now}')
value = func(*args, **kwargs)
print(f'Execution time is {datetime.now()-now}')
return value
""" 返回這個新的函式 """
return new_function
""" 裝飾符的使用 """
@record_time
def circle_area(radius):
area = 3.14*radius**2
print(f'Area of circle with radius {radius} is {area}')
return area
circle_area(3)
該裝飾符就放在函式定義前一行, 其意義等同於把 circle_area 函式更改為我們在 record_time 中所定義的新函式.
def circle_area(radius):
area = 3.14*radius**2
print(f'Area of circle with radius {radius} is {area}')
return area
""" circle_area 被取代為 record_time 中定義的 decorator """
circle_area = record_time(circle_area)
所以我們表面上用的是 circle_area, 事實上, 它已經被改成我們所定義的另一個新函式.
裝飾函式的引數
裝飾符的使用有兩種方式, 類似 function 代表函式本身, 而 function() 代表函式呼叫後的結果. 以下例為例子:
@record_time 表示提供一個函式 record_time 來作包裝, 等同
circle_area1 = record_time(circle_area1) 也就是 wrapper
@record_time(n=2) 表示呼叫 record_time(n=2) 的結果來取代原函式, 等同
circle_area2 = record_time(n=2)(circle_area2)
>>> from datetime import datetime
>>>
>>> def record_time(_func=None, n=1):
>>> """ n 代表重複的次數 """
>>> def decorator(func):
>>> def wrapper(*args, **kwargs):
>>> now = datetime.now()
>>> print(f'{func.__name__} called at {now}')
>>> for i in range(n):
>>> value = func(*args, **kwargs)
>>> print(f'Execution time is {datetime.now()-now}')
>>> return value
>>> return wrapper
>>> if _func is None:
>>> return decorator
>>> else:
>>> return decorator(_func)
>>>
>>> @record_time
>>> def circle_area1(radius):
>>> area = 3.14*radius**2
>>> print(f'Area of circle with radius {radius} is {area}')
>>> return area
>>>
>>> @record_time(n=2)
>>> def circle_area2(radius):
>>> area = 3.14*radius**2
>>> print(f'Area of circle with radius {radius} is {area}')
>>> return area
>>>
>>> circle_area1(3)
>>> circle_area2(3)
circle_area1 called at 2020-06-12 09:50:52.705339
Area of circle with radius 3 is 28.26
Execution time is 0:00:00.010033
circle_area2 called at 2020-06-12 09:50:52.718341
Area of circle with radius 3 is 28.26
Area of circle with radius 3 is 28.26
Execution time is 0:00:00.019950
裝飾函式的屬性
裝飾函式可以加上屬性, 用來記錄狀態. 如下例可以記錄函式被呼叫的次數.
在本例子中, 如果裝飾的函式不同, 其呼叫的次數都會被累加在一起. 如果你想區分不同函式被呼叫的次數, 可以自建一個以 function.__name__:count_value
為鍵值對的字典分開統計.
>>> def times(func):
>>> def decorator(*args, **kwagrs):
>>> times.count += 1
>>> print(f'This function called {times.count} times.')
>>> value = func(*args, **kwagrs)
>>> return value
>>> times.count = 0
>>> return decorator
>>>
>>> @times
>>> def circle_area(radius):
>>> area = 3.14*radius**2
>>> print(f'Area of circle with radius {radius} is {area}')
>>> return area
>>>
>>> circle_area(3)
>>> circle_area(4)
>>> circle_area(5)
This function called 1 times.
Area of circle with radius 3 is 28.26
This function called 2 times.
Area of circle with radius 4 is 50.24
This function called 3 times.
Area of circle with radius 5 is 78.5
裝飾符的疊加使用
疊加使用裝飾符, 以下例
>>> @decorator_third
>>> @decorator_second
>>> @decorator_first
>>> def circle_area(radius):
>>> area = 3.14*radius**2
>>> print(f'Area of circle with radius {radius} is {area}')
>>> return area
其結果如同
circle_area = decorator_third(decorator_second(decorator_first(circle_area)))
類的裝飾符
類的裝飾符建立方式有兩種, 一種類似函式的使用, 另一種為了可以呼叫, 必須建立一個 __call__ 函式, 這裡就不細說了, 有興趣的再自行看一下 Python 的檔案說明.
函式的代表符
旣然函式都被取代了, 你看到的函式代表符就不再是原函式的名稱
>>> circle_area
<function record_time.<locals>.new_function at 0x000000000742E9D0>
那怎麼辦呢, 一樣, 再加個裝飾符, 當然你可以自己設計, 也可以使用現成的@functools.wraps(func)
from datetime import datetime
import functools
def record_time(func):
""" 新增裝飾符 """
@functools.wraps(func)
def new_function(*args, **kwargs):
now = datetime.now()
print(f'func called at {now}')
value = func(*args, **kwargs)
print(f'Execution time is {datetime.now()-now}')
return value
return new_function
@record_time
def circle_area(radius):
area = 3.14*radius**2
print(f'Area of circle with radius {radius} is {area}')
return area
circle_area(3)
>>> circle_area
<function circle_area at 0x00000000082EE940>
本作品採用《CC 協議》,轉載必須註明作者和本文連結