玩玩Python的Decorator與closure

Python的Decorator功能平常沒那麼常用

今天稍微針對他的用法做點筆記

簡單來說,我可以在一個函數或變數宣告的時候,用打包起來
替他賦予一個新功能,有點類似proxy

def before(func):
    print "before"
    return func

@before
def func1():
    print "run"

func1()
func1()

上面的範例before會得到function的宣告,這裡直接回傳

輸出結果:

before
run
run

woo..看樣子直接回傳的話是在宣告的時候直接執行了before

這裡必須做個wrapper將funcion重新打包,而不能直接回傳

def before(func):
    def exec_run():  #wrapper function

        print "before"
        func()
        print "after"
    return exec_run

@before
def func1():
    print "run"

func1()
func1()

輸出結果:

before
run
after
before
run
after

成功了!!,就這麼簡單,完成了在java裡實現麻煩的pattern

甚至想包多少就包多少wrapper

def before(func):
    def exec_run():
        print "before"
        func()
        print "after"
    return exec_run

def before2(func):
    def exec_run():
        print "before2"
        func()
        print "after2"
    return exec_run

@before
@before2
def func1():
    print "run"

func1()
func1()

輸出結果:

before
before2
run
after2
after
before
before2
run
after2
after

想帶參數及回傳?那也很容易

下面一個例子示範萬用的wrapper

def mywrap(func):
    def wrapper(*args, **kwargs): #萬用參數宣告

        print "this is a wrapper"
        result = func(*args, **kwargs)
        return result
    return wrapper

@mywrap
def hello(name):
    return "hello %s" % name

print hello('Tom')

使用python標準函式宣告可以接受任何形態的參數

輸出結果:

this is a wrapper
hello Tom

如果我想用類似Spring一樣帶參數使用方法呢

@RequestMapping("/mypage")
def handle_mypage():
...

帶參數的話會稍微複雜些,得搭配functools使用

def RequestMapping(path):
    def receive_func(func):
        import functools
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print "handle %s..." % path
            return func(*args, **kwargs)
        return wrapper
    return receive_func

@RequestMapping("/mypage")
def handle_mypage():
   return "<b>mypage</b>"

print handle_mypage()

output:

handle /mypage...
<b>mypage</b>

關於functools請參考https://docs.python.org/3/library/functools.html

現在高階語言的威力是讓人用了就愛不釋手啊

若是一些早期的OOP語言想實現這功能必須用proxydecorator等desgin pattern
必須創造一些共同的interface才能實現

但是在現代高階程式語言通常已經將這些design pattern的特性融合到語法裡面了,不再需要辛苦的Refactoring to Patterns,因為語言本身就已經是design pattern

上面瞎扯淡了一些,回頭看看python的closure,在functional programming裡面closure是很棒的一個應用
既然都可以wrapper了當然也要配合closure才好玩

def mywrap(func):
    warpname = "mywrapper"
    def wrapper(*args, **kwargs):
        print warpname
        result = func(*args, **kwargs)
        return result
    return wrapper

@mywrap
def hello(name):
    return "hello %s" % name

print hello('Tom')

#output
mywrapper
hello Tom

在javascript裡面在平常不過的code,但是在python裡面卻藏有陷阱
那就是直接宣告的變數是不能修改的

def mywrap(func):
    warpname = "mywrapper"
    def wrapper(*args, **kwargs):
        print warpname
        warpname = "inner" 這裡嘗試修改
        result = func(*args, **kwargs)
        return result
    return wrapper
    
output
UnboundLocalError: local variable 'warpname' referenced before assignment

比較粗略的解決方案是讓它變成wrapprt的一個屬性

def mywrap(func):
    def wrapper(*args, **kwargs):
        print wrapper.warpname
        wrapper.warpname = "inner"
        result = func(*args, **kwargs)
        return result
    wrapper.warpname = "mywrapper" #變成wrapper的一個屬性

    return wrapper

@mywrap
def hello(name):
    return "hello %s" % name


print hello('Tom')
print hello('Tom')

output:

mywrapper
hello Tom
inner  #修改成功

hello Tom

這裡還真是小缺陷,不能更直接的使用

做個小結吧

當了四年的java工程師,開始玩轉python, scala, clojure等更高階的語言之後

才真正體會到java的笨重,有人說程式語言精通一兩種就好,共通概念是一樣的

但是在現在日新月異的時代,許多新的想法層出不窮

每年都有新的程式崛起,我更傾向之前看過的一句話

身為工程師,一年要學一種程式語言

當然精通的老本行不能棄,但是每種程式語言想要表達的內涵不盡相同

這裡的學不是指學程式的語法,而是去學著欣賞該程式語言表達內涵跟語言設計的動機

看看scala怎麼在表達reactive,看看R跟Python怎麼對資料進行操作

多學習一些不同程式語言的長處,更能開拓工程師的眼界

套句行家的話:用Python寫程式跟寫Python的程式是兩回事

Code Like a Pythonista: Idiomatic Python

這是學python很常面臨的一個課題,如何寫才像python

明明都用python了卻還寫得像java的工程師大有人在

comments powered by Disqus