[Python] Python decorator

python decorator, 一個相當方便而且相當重要的工具, 我trace很多python都會用到此功能

官方網站說明: http://www.python.org/dev/peps/pep-0318/

有一篇中文教學也很詳盡

http://blog.roodo.com/descriptor/archives/9206319.html

這篇教學跟我看的來源一樣是Bruce Eckel寫的教學, 其實英文好的看他寫的就夠了, 我只是比較囉唆的翻譯

http://www.artima.com/weblogs/viewpost.jsp?thread=240808


這篇我有點算是Eckel的翻譯版本, 範例我會拿他的範例做介紹, 因為我寫不出比他更好的範例(遮臉)

不過我加入較多的個人解說, 用來加深我自己印象.

Eckel 從C的macro講起, python的decorator很接近同樣的行為, 或者說, 你可以利用decorator去modify你原本的function code, 甚至是modify class

先講怎麼在python使用decorator (使用Eckel的範例)

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

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

function decorator: 上面加上一行@decorator_name就可以了 (當然這樣還不夠會error)

使用方法是這樣, 但是Eckle直接從更強的功能去描述如何使用decorator, 首先是用using class as decorator

下面一樣用Eckel範例

class myDecorator(object):

    def __init__(self, f):
        print "inside myDecorator.__init__()"
        f() # Prove that function definition has completed

    def __call__(self):
        print "inside myDecorator.__call__()"

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

print "Finished decorating aFunction()"

aFunction()

前面已經說到, 用decorator可以去修改原本既有的function code, 甚至是class

所以這邊, 他就利用decorator去修改了aFunction()的能力, 我們先看一下輸出結果

Result:
inside myDecorator.__init__()
inside aFunction()
Finished decorating aFunction()
inside myDecorator.__call__()

這邊發現有趣的執行順序, 首先@myDecorator(第10行)的時候, 就會先去呼叫myDecorator的__init__(), 除了印出inside myDecorator.__init__()之外, 也真的去呼叫了f() (也就是原本的aFunction()), 之後離開印出Finished decorating aFunction(), 有趣的來了, 這時候真的呼叫了aFunction(), 他跑得卻是myDecorator的__call__(), 透過這樣的範例, 應該是已經了解如何利用decorator去修改原本既有的function code, 也就是說透過decorator可以利用__call__去replace掉原本既有的code所作的事情, 只要在function上面加decorator, 並function要return出一個callable function讓decorator mechanism可以使用跟呼叫, 所以他在entryExit function宣告了一個new_f, 並且將上述要做的進入輸出跟離開輸出資訊都寫在這邊, 而entryExit的回傳就是new_f, 相當簡單就做完了要做的事情, 之後就是等著要用的function呼叫即可,

這邊比較要注意的地方是, 在外面call func1.__name__, 出來的居然是new_f, 因為前面已經說了, 他回傳了new_f出來, 後面其實執行的都是new_f裡面實作的東西, 如果希望還是可以印出func1的話, 可以在return new_f之前先把new_f.__name__強制改掉變成 new_f.__name__ = f.__name__ 然後再return出去且實作decorator的行為即可

再來看Eckle提到的另外一個也滿有用的用途, 假設我今天想要我的function不管是進入或者是離開都要有訊息提示, 則我可以這麼做

class entryExit(object):

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

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

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

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

func1()
func2()

未看先猜看, 再來看結果

Result:
Entering func1
inside func1()
Exited func1
Entering func2
inside func2()
Exited func2

可以看出, 相當完美而且簡潔的做到想要的結果, 只要記得在init的時候先設定好f(), 之後再呼叫即可, 簡潔, 算是decorator最好的結果

當然比較常見的另外一種範例是, using function as decorator, 然後改寫上述範例, 也就是說我今天不用class as decorator, 我用function as decorator來做出上述一樣, 進出function我都要印出訊息要如何實作

以下是Eckle的範例

def entryExit(f):
    def new_f():
        print "Entering", f.__name__
        f()
        print "Exited", f.__name__
    return new_f

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

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

func1()
func2()
print func1.__name__

Result:
Entering func1
inside func1()
Exited func1
Entering func2
inside func2()
Exited func2
new_f

這裡比較特別, 這邊是call到func1()的時候, 才有實際去呼叫entryExit()這個function, 也就是上述的16, 17行

在這邊, entryExit function要return出一個callable function讓decorator mechanism可以使用跟呼叫, 所以他在entryExit function宣告了一個new_f, 並且將上述要做的進入輸出跟離開輸出資訊都寫在這邊, 而entryExit的回傳就是new_f, 相當簡單就做完了要做的事情, 之後就是等著要用的function呼叫即可

這邊比較要注意的地方是, 在外面call func1.__name__, 出來的居然是new_f, 因為前面已經說了, 他回傳了new_f出來, 後面其實執行的都是new_f裡面實作的東西, 如果希望還是可以印出func1的話, 可以在return new_f之前先把new_f.__name__強制改掉變成 new_f.__name__ = f.__name__ 然後再return出去

前半段講的都是decorator without argument, 意思就是也有decorator with argument

下面會開始講兩者的差異, (Ref Eckel: http://www.artima.com/weblogs/viewpost.jsp?thread=240845)

先給個Eckel的範例

class decoratorWithArguments(object):

    def __init__(self, arg1, arg2, arg3):
        """
        If there are decorator arguments, the function
        to be decorated is not passed to the constructor!
        """
        print "Inside __init__()"
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3

    def __call__(self, f):
        """
        If there are decorator arguments, __call__() is only called
        once, as part of the decoration process! You can only give
        it a single argument, which is the function object.
        """
        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

@decoratorWithArguments("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"

來看看結果

Result:
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

看起來超多東西, 不過關鍵很單純, 在decorate with argument的時候(ex: @decoratorWithArguments("hello", "world", 42)), class decoratorWithArguments呼叫了__init__, 馬上又呼叫了__call__, 可是雖然裡面有def wrapped_f跟return wrapped_f, 但是並不會立即執行, 他會等到actual call function的時候, 才會真的去運作, 所以會看到在call sayHello("say", "hello", "argument", "list"), 才真正印出了__call__所做的事情, 注意, function本身的arguments記得要在設定的callable function當作argument傳進去(ex: wrapped_f(*args)的*args), 之後使用才有辦法正常使用, 後面就沒什麼差了

function as decorator with argument基本上是一樣的使用狀況, 除了要return出去的callable function之外, 其餘有在function as decorator的其他行為都會先被執行

ex:
def decoratorFunctionWithArguments(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的時候, 會先呼叫print "Inside wrap()"這行, 但一樣要等到呼叫到被decorator的function, 才會開始執行wrapped_f的功能

留言

這個網誌中的熱門文章

[Linux] Linux下查詢硬體記憶體資訊 Memory Information

[Other] Chrome 重新整理所有開啟頁面

[Python] Simple Socket Server