這篇算是改寫自 A primer on Python decorators,不算是翻譯文,因為我懶得照翻 XD 不過例子跟文章結構都是參考這篇文章中擷取出來的。
引子
在 Python 當中,functions 是 first-class objects。也就是說你可以對 function 做任何跟其他 objects 一樣的事情。舉例來說,你可以把一個 function 指定到一個變數當中:
>>> def addone(n): ... return n + 1 >>> addone(3) 4 >>> add1 = addone >>> add1(3) 4
這樣看來可能沒什麼,但是當你可以把某個 function 傳入另外一個 function 之後再回傳時,我們可以發現這會有很多有趣的應用。舉例來說,在 Python 裡面有內建 map 這個 function,你只要傳入一個 function 以及一個 list,map 就會回傳一個新的 list,回傳的 list 內容是把你原本傳入的 list 當做輸入,傳進去 function 之後回傳的結果。以下是個例子:
>>> numbers = [1,2,3,4] >>> map(addone, numbers) [2, 3, 4, 5]
這樣可以幹啥?
我們透過了 map 這個簡單的例子來示範這麼做的好處,接下來我們來展示一個更棒的例子。
Fibonacci number 的定義如下:
如果用 Python 來寫,你可能會寫出下面的 code:
def fib(n): if n in [0,1]: return n else: return fib(n-1) + fib(n-2)
這邊我們可以看到,當計算 fib(10) 的時候,會去計算 fib(9) 和 fib(8)。計算 fib(9) 的時候會計算 fib(8) 和 fib(7),這邊我們可以看到 fib(8) 的值被重複計算到了。所以我們希望把重複算過的值記錄下來,這樣就可以省下很多計算的時間,這個技巧叫做 memoization 。
要達到這個目的,一個簡單的想法是在 fib function 當中用個 dictionary 把算過的值記下來,如果要求的值已經算過了,就回傳存過的值,不過這方法麻煩的地方就是在於還要修改 fib 的程式碼才能達到這個效果。在 Python 當中,我們可以有更好的方法!既然 function 也可以當做回傳值,我們可以把傳入的 function 修改成會把結果記下來的版本再回傳,下面是這個 function 可能的樣子:
def memoize(fn): stored = {} def memoized(*args): try: return stored[args] except KeyError: result = fn(*args) stored[args] = result return result return memoized
這麼一來,我們要產生有 memoization 效果的 fib function 可以這麼做:
fib = memorize(fib)
這跟 decorator 有啥關係?
由於這種 pattern 常常出現,所以 Python 當中提供了 decorators 這個機制來讓你更簡單的做這種事情。
@memoize def fib(n): if n in [0,1]: return n else: return fib(n-1) + fib(n-2)
在這邊,我們會說 memoize 是個 decorator,它 decorate 了 fib 這個 function。其實這邊做的事情跟上面的例子沒有什麼不同,只是透過了 @ 這個 operator 來減少了 coding 的麻煩。
在 decorator 當中,我們也可以傳入參數。舉例來說,如果我們希望用 memcached 來存已經算好的結果,我們可能會寫成下面的樣子:
@memcached('127.0.0.1:11211') def fib(n): if n in [0,1]: return n else: return fib(n-1) + fib(n-2)
這樣寫其實就等同於
fib = memcached('127.0.0.1:11211')(fib)
結論
這邊很簡單了介紹了 Python 當中的 decorator,相信看完之後對 decorator 應該有些初步的了解。Python 當中 decorator 是個很常見的樣式,在各種 framework 當中處處可見。透過 decorator 的應用,將可以讓你的程式碼語義更加的簡潔易懂。