python 的 decorator 功能,使用的語法看起來很像 Java 的 annotation,是在 function 前面加上 @decorator,雖然名稱是 decorator,但實際上比 Decorator Design Pattern 的功能還要多一點。
decorator class
function decorator 使用起來,就是直接在 function 定義前面,加上 @my_decorator。
@my_decorator
def aFunction():
print("inside aFunction()")
實際上,當 compiler 編譯 aFunction 時,會將 aFunction 整個 function 傳入 myDecorator,有取代原本 aFunction 的感覺,也有點像是 my_decorator(aFunction)
這樣的意思。
要實作 my_decorator 必須要實作 __init__
以及 __call__
這兩個 function,__init__
會在一開始就被呼叫,而 __call__
是在結束時被呼叫。
class my_decorator(object):
def __init__(self, f):
print("inside my_decorator.__init__()")
f()
def __call__(self):
print("inside my_decorator.__call__()")
@my_decorator
def aFunction():
print("inside aFunction()")
print("Finished decorating aFunction()")
aFunction()
執行結果如下,在程式中,因為 decorator 初始化的關係,會先呼叫 my_decorator.__init__()
,在這個函數裡面,會取得 aFunction 的函數定義,所以可以在裡面呼叫 aFunction,在 aFunction 結束後,才列印了 Finished
的部分。
而最後是因為呼叫 aFunction()
,所以轉為呼叫 my_decorator.__call__()
。
inside my_decorator.__init__()
inside aFunction()
Finished decorating aFunction()
inside my_decorator.__call__()
也就是說,decorator 是在 init 的時候,就已經取得了 aFunction 的定義,而不是在呼叫 aFunction 時,才傳入 my_decorator。
decorator 其實就只是將 function 傳入另一個 function 的 syntax sugar。
在剛剛的例子中,我們在 __init__
直接呼叫了 f(),但以下這個例子,才是比較重要的功能,他先將 f 儲存到變數中,然後等到 __call
才使用 f()
class entry_exit(object):
def __init__(self, f):
self.f = f
def __call__(self):
print("Entering", self.f.__name__)
self.f()
print("Exited", self.f.__name__)
@entry_exit
def func1():
print("inside func1()")
@entry_exit
def func2():
print("inside func2()")
func1()
func2()
所以可以在呼叫 func1()
的前面以及執行完成的後面,在 __call__
裡面呼叫 self.f()
的前後加上自己需要增加的 code
Entering func1
inside func1()
Exited func1
Entering func2
inside func2()
Exited func2
function as decorator
剛剛是使用 class 作為 decorator,但可以直接定義一個 decorator function,以下的例子中,定義了 decorator function 以 f 為參數,在裡面定義了新的 new_f
,取代了原本的 f
def entry_exit(f):
def new_f():
print("Entering", f.__name__)
f()
print("Exited", f.__name__)
return new_f
@entry_exit
def func1():
print("inside func1()")
@entry_exit
def func2():
print("inside func2()")
func1()
func2()
print(func1.__name__)
執行結果為,因為 new_f
已經取代了 f,所以 func1.__name__
的名稱結果是 new_f
Entering func1
inside func1()
Exited func1
Entering func2
inside func2()
Exited func2
new_f
如果覺得奇怪,還是可以刻意將 new_f
的名稱修改為跟 f 一樣
def entry_exit(f):
def new_f():
print("Entering", f.__name__)
f()
print("Exited", f.__name__)
new_f.__name__ = f.__name__
return new_f
Decorator with Arguments
因為 decorator 其實也是呼叫一個 function,所以可以直接在 decorator 上加上參數,以下是在 decorator class 裡面取得 decorator 參數的例子
class decorator_with_arguments(object):
def __init__(self, arg1, arg2, arg3):
print("Inside __init__()")
self.arg1 = arg1
self.arg2 = arg2
self.arg3 = arg3
def __call__(self, f):
print("Inside __call__()")
def wrapped_f(*args):
print("Inside wrapped_f()")
print("Decorator arguments:", self.arg1, self.arg2, self.arg3)
f(*args)
print("After f(*args)")
return wrapped_f
@decorator_with_arguments("hello", "world", 42)
def sayHello(a1, a2, a3, a4):
print('sayHello arguments:', a1, a2, a3, a4)
print("After decoration")
print("Preparing to call sayHello()")
sayHello("say", "hello", "argument", "list")
print("after first sayHello() call")
sayHello("a", "different", "set of", "arguments")
print("after second sayHello() call")
執行結果如下,在 __init__
,可以取得 decorator 的參數,並在 __call__
裡面使用這些參數
Inside __init__()
Inside __call__()
After decoration
Preparing to call sayHello()
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: say hello argument list
After f(*args)
after first sayHello() call
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: a different set of arguments
After f(*args)
after second sayHello() call
function decorator with arguments
同樣的 function decorator 也可以使用參數
def decorator_function_with_arguments(arg1, arg2, arg3):
def wrap(f):
print("Inside wrap()")
def wrapped_f(*args):
print("Inside wrapped_f()")
print("Decorator arguments:", arg1, arg2, arg3)
f(*args)
print("After f(*args)")
return wrapped_f
return wrap
@decorator_function_with_arguments("hello", "world", 42)
def sayHello(a1, a2, a3, a4):
print('sayHello arguments:', a1, a2, a3, a4)
print("After decoration")
print("Preparing to call sayHello()")
sayHello("say", "hello", "argument", "list")
print("after first sayHello() call")
sayHello("a", "different", "set of", "arguments")
print("after second sayHello() call")
Inside wrap()
After decoration
Preparing to call sayHello()
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: say hello argument list
After f(*args)
after first sayHello() call
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: a different set of arguments
After f(*args)
after second sayHello() call
因為被 decorator 修飾的原始 function,無法在實作 decorator 時就預先知道參數的個數,因此在定義 wrapper function 時,是定義為 def wrapped_f(*args):
。
其中 *args
代表不固定個數的參數,如果要支援有參數名稱的參數,則可以使用 **kwargs
多個 decorator
@a
@b
@c
def f ():
pass
如果有好幾個 decorator,那麼實際上呼叫的順序,是以下這樣
f = a(b(c(f)))
處理時間的 decorator
以下這是計算函數處理時間的 decorator
from time import time
def timer(func):
def wraper(*args, **kwargs):
before = time()
result = func(*args, **kwargs)
after = time()
print("elapsed: ", after - before)
return result
return wraper
@timer
def add(x, y = 10):
return x + y
@timer
def sub(x, y = 10):
return x - y
print("add(10)", add(10))
print("add(20, 30)", add(20, 30))
執行結果為
elapsed: 9.5367431640625e-07
add(10) 20
elapsed: 1.1920928955078125e-06
add(20, 30) 50
函數參數的型別檢查 pylimit
python 因為是動態資料型別語言,變數不需要定義資料型別,隨時會改變,但是在實作時,常常會遇到一個問題,就是實作的 function 只能處理某些資料型別的參數,程式如果寫錯,很容易就 crash。
上面的兩個 pylimit 套件,就是利用 decorator 的做法,在裡面加上 function 參數的資料型別定義,然後就可以在呼叫該函數時,進行資料型別檢查,這樣就不需要在實作時,寫上很多 type() 檢查的程式碼。
沒有留言:
張貼留言