到底為何需要裝飾器? (1)

Pizza
Pizza’s
Published in
8 min readFeb 26, 2020

Github連結:

https://github.com/cabie8399/NOTE--Python_Bible/blob/master/99_%E5%85%B6%E4%BB%96Python%E6%96%87%E7%AB%A0/%E5%88%B0%E5%BA%95%E7%82%BA%E4%BD%95%E9%9C%80%E8%A6%81%E8%A3%9D%E9%A3%BE%E5%99%A8(1).ipynb

1. 裝飾器是什麼?

裝飾器本質上是一個 Python 函數或類,它可以讓其他函數或類在不需要做任何代碼修改的前提下增加額外功能,
裝飾器的返回值也是一個函數/類對象。它經常用於有切面需求的場景,比如:插入日誌、性能測試、事務處理、緩存、權限校驗等場景,
裝飾器是解決這類問題的絕佳設計。有了裝飾器,我們就可以抽離出大量與函數功能本身無關的雷同代碼到裝飾器中並繼續重用。

首先我們先看一個簡單的代碼:

In [36]:

# 1-50000000加總
def add():
a = 0
n = 1
while n <= 50000000:
a += n
n += 1

return("加總結果: {}".format(a))
add()

Out[36]:

'加總結果: 1250000025000000'

上述為一個從1加總到50的函數
假設我們要為某這個代碼測量他的執行時間和
那我們一般會怎麼做呢?

In [35]:

import time# 1-50000000加總
def add():
s_time = float(time.clock())
a = 0
n = 1
while n <= 50000000:
a += n
n += 1

e_time = float(time.clock())


return("加總結果: {} ,執行時間 => {} 秒".format(a , e_time-s_time))
add()

Out[35]:

'加總結果: 1250000025000000 ,執行時間 => 3.252321199999983 秒'

雖然這樣可以做出我們想要的結果,但因為計算執行時間,並不是我們主要功能
利用這樣的寫法,並不是最簡潔有力,況且若我們中間有好多函數都想計算它的運算時間的話
這樣會造成程式碼重複率高,也破壞主要功能的程式碼,並不是一個方法

2. 裝飾器出場

好險Python中有一個叫做裝飾器的東西,主要功能就是為我們原有的函數增加新功能
像上述例子,加總1–50000000是我們原有的函數,但我想為他增加計算執行時間功能,我們可以這樣改寫:

In [52]:

import time
# 計算執行時間函數
def exec_time(func):
def wrapper():
#func_name = func.__name__
s_time = float(time.clock())
total = func()
e_time = float(time.clock())
return ("執行函數: {} , 加總結果: {}, 執行時間: {}秒".format(func.__name__ ,total,e_time-s_time))
return wrapper

# 1-50000000加總
@exec_time
def add():
a = 0
n = 1
while n <= 50000000:
a += n
n += 1
#return ("加總結果: {}".format(a))
return a
# 1-30000000加總
@exec_time
def add2():
a = 0
n = 1
while n <= 30000000:
a += n
n += 1
#return ("加總結果: {}".format(a))
return a
print(add())
print(add2())
執行函數: add , 加總結果: 1250000025000000, 執行時間: 3.2631033000000116秒
執行函數: add2 , 加總結果: 450000015000000, 執行時間: 2.1447957000000315秒

3. *args、**kwargs

若假設今天原有的func,傳入許多參數,那該怎麼辦呢?

一般函數的參數個數都是固定的,但如果遇到參數數量不固定的狀況,通常會將某些參數填上預設值,
在 python function 可以支援兩種可變數量的參數 *args 和 **kwargs。

In [58]:

import time
# 計算執行時間函數
def exec_time(func):
def wrapper(*args):
#func_name = func.__name__
s_time = float(time.clock())
res = func(*args)
e_time = float(time.clock())
return ("執行函數: {} , 結果: {}, 執行時間: {}秒".format(func.__name__ ,res,e_time-s_time))
return wrapper
# 打印一句話
@exec_time
def foo(name, age=None, height=None):
return ("I am %s, age %s, height %s" % (name, age, height))


print(foo("pizza",5))
執行函數: foo , 結果: I am pizza, age 5, height None, 執行時間: 2.6999996407539584e-06秒

或以下寫法 *args, **kwargs

In [59]:

import time
# 計算執行時間函數
def exec_time(func):
def wrapper(*args, **kwargs):
#func_name = func.__name__
s_time = float(time.clock())
res = func(*args, **kwargs)
e_time = float(time.clock())
return ("執行函數: {} , 結果: {}, 執行時間: {}秒".format(func.__name__ ,res,e_time-s_time))
return wrapper
# 打印一句話
@exec_time
def foo(name, age=None, height=None):
return ("I am %s, age %s, height %s" % (name, age, height))



print(foo("pizza",5))
執行函數: foo , 結果: I am pizza, age 5, height None, 執行時間: 3.999999989900971e-06秒

補充說明 *args 和 **kwargs

*args是可變的positional arguments列表,*kwargs是可變的keyword arguments列表。
兩個可以同時使用,但在使用時,\
args必須在**kwargs的前面,因為positional arguments,
有位置順序的對應,必須位於keyword arguments之前。

In [61]:

def fun(a, *args):
print("a={}".format(a))
for arg in args:
print('Optional argument: {}'.format(arg))
fun(1,22,33)a=1
Optional argument: 22
Optional argument: 33

如果同時加上 **kwargs

In [62]:

def fun(a, *args, **kwargs):
print("a={}".format(a))
for arg in args:
print('Optional argument: {}'.format( arg ) )
for k, v in kwargs.items():
print('Optional kwargs argument key: {} value {}'.format(k, v))
fun(1,22,33, k1=44, k2=55)a=1
Optional argument: 22
Optional argument: 33
Optional kwargs argument key: k1 value 44
Optional kwargs argument key: k2 value 55

4. 類裝飾器

沒錯,裝飾器不僅可以是函數,還可以是類,相比函數裝飾器,類裝飾器具有靈活度大、高內聚、封裝性等優點。
使用類裝飾器主要依靠類的call方法,當使用 @ 形式將裝飾器附加到函數上時,就會調用此方法。

In [63]:

class Foo(object):
def __init__(self,func):
self._func = func

def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')


@Foo
def bar():
print ('bar')
bar()class decorator runing
bar
class decorator ending

5. 裝飾器順序

In [ ]:

@a
@b
@c
def f ():
pass

它的執行順序是從裡到外,最先調用最裡層的裝飾器,最後調用最外層的裝飾器,它等效於

In [ ]:

f = a(b(c(f)))

--

--