11 July, 2009

Python Idiom: Decorator II

之前的部落格 介紹了 decorator 的基本概念及用法,本篇介紹 decorator 的兩個應用。 第一個例子是 幫函式加上快取
def fibonacci(n):
   if n in (0, 1):
      return n
   return fibonacci(n-1) + fibonacci(n-2)
定義下面的 decorator
class memoized(object):
   def __init__(self, func):
      self.func = func
      self.cache = {}
   def __call__(self, *args):
      try:
         return self.cache[args]
      except KeyError:
         self.cache[args] = value = self.func(*args)
         return value

使用方法很簡單在函式定義上一行加上 @memoized 即可,之後呼叫 fibonacci(n) 時, 程式會先檢查快取中有沒有這個值,如果有就傳回, 如果沒有就呼叫 fibonacci(n),之後把計算得到值存入快取,再傳回。 之後如果我們想要替任何一個函式加上快取,只要在函式定義上一行加上 @memoized,而不需要更動函式實作,是不是很方便呢?

第二個例子,我覺得很妙,考慮
def compute(x):
    return x**3 - 1
此函式的輸入是一個數字,如果輸入想改成數字串列的話, 一般的想法是直接更改函式實作,可是如果有很多類似函式, 一個一個改就嫌累贅,這時 decorator 就派上用場了,定義
def elementwise(fn):
    def newfn(arg):
        if hasattr(arg,'__getitem__'):  # is a sequence
            return type(arg)(map(fn, arg))
        else:
            return fn(arg)
    return newfn

在 compute() 定義上一行加上 @elementwise, 函式就可以接受數字串列了。 之後呼叫 compute(x) 時,函式會判斷輸入變數是不是串列, 如果不是的話,就直接傳回 compute(x),如果是的話, 會產生一個新的串列,其元素是 compute(y),y 是 x 其中一個元素。 (如果對 map 不熟的話,可以參考 之前的部落格,其實不用 map,改用 list comprehension 也可以。) 由於 map 的傳回值型態是 list,為了讓傳回值型態跟輸入型態一致, 所以 map 前要加上 type(arg) 轉型。

註: 第一個例子來自 Python Decorator Library 的第三個範例,而第二個例子來自 Charming Python 的 Listing 12。

No comments: