2012年4月27日 星期五

快快樂樂學會 Python decorators

這篇算是改寫自 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 的定義如下:

Fibonacci number

Fibonacci seed

如果用 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 的應用,將可以讓你的程式碼語義更加的簡潔易懂。

2 則留言:

Related Posts Plugin for WordPress, Blogger...