11 June, 2009

Python Idiom: Decorator I

本文將介紹 Python Decorators 的用法,主要內容是根據我的理解部分改寫 Bruce Eckel 的好文 Decorators I: Introduction to Python Decorators 。假設我們有下面兩個函式
def func1():
    print "inside func1()"

def func2():
    print "inside func2()"

我們希望在呼叫 func1() 前顯示 Entering func1, 而在離開 func1() 後顯示 Exited func1, 對 func2() 的要求也是相同,只是把函式名字換成 func2, 最直接的方法是修改 func1 及 func2 的實作內容, 可是這樣做實在太累贅了, 有沒有辦法把 func1 跟 func2 當作物件, 自己寫一個額外的函式來「裝飾」這些物件? Python 語言中所提供的 decorator 語法可以幫我們達到這個目的 (decorator 就是裝飾者的意思)。 decorator 的使用語法如下

@myDecorator
def func1():
    print "inside func1()"

@myDecorator
def func2():
    print "inside func2()"

亦即在函式定義的前一行加上 @myDecoratormyDecorator 是一個我們即將實作的類別或是函式的名字, 實作的內容決定如何裝飾 func1 及 func2。實作內容如下

class myDecorator(object):

    def __init__(self, f):
        self.f = f

    def __call__(self):
        print "Entering", self.f.__name__
        self.f()
        print "Exited", self.f.__name__

myDecorator 實際上就是把函式當作輸入, 而傳回值一定要是一個可呼叫的物件, 所以一定要定義 __call__ 這個函式, 如何裝飾輸入函式就是在 __call__ 中的實作完成。

熟悉 Python 的人會說,沒有 decorator 語法一樣可以完成相同的要求, 如 func1 = modifyFunc(func1) 亦即定義一個以函式為輸入的函式 modifyFunc, 把 modifyFunc 裝飾過後的結果 (必須是函式) 當做傳回值指定給原先輸入的函式。 可是有了 decorator 語法,就不需要指定的動作, 只需要在函式的定義前一行加上 @myDecorator。 其實 @ 只是一個便利的語法 (syntax sugar) 而已, 其作用是將一個函式 func1 當作另一個函式 myDecorator 的輸入, 並將 myDecorator 傳回值指定給原先的函式 func1。decorator 之所以有 那麼多的應用 是因為這個便利的語法改變了我們對程式設計的看法。 藉由成為程式語言的一項語法,decorator 將函式當做輸入物件的想法 變為主流的思考方式。

此外,decorator 還可以串起來 (chained decorator),比如

@decorator1
@decorator2
def myfunc():
     pass
上面的程式碼等價於
myfunc = decorator1(decorator2(myfunc))
其實 decorator 可以擁有參數,例如
@decorator(arg1)
def myfunc(arg2):
     pass

想要了解具有參數的 decorator 可以參考 Python Decorators II: Decorator Arguments 。 之後,我會介紹幾個關於 decorator 應用的例子。

No comments: