Python裝飾器的函數語言程式設計
Python的修飾器的英文名叫Decorator,當你看到這個英文名的時候,你可能會把其跟Design Pattern裡的Decorator搞混了,其實這是完全不同的兩個東西。雖然好像,他們要乾的事都很相似——都是想要對一個已有的模組做一些“修飾工作”,所謂修飾工作就是想給現有的模組加上一些小裝飾(一些小功能,這些小功能可能好多模組都會用到),但又不讓這個小裝飾(小功能)侵入到原有的模組中的程式碼裡去。但是OO的Decorator簡直就是一場惡夢,不信你就去看看wikipedia上的詞條(Decorator Pattern)裡的UML圖和那些程式碼,這就是我在《 從面向物件的設計模式看軟體設計
Python 的 Decorator在使用上和Java/C#的Annotation很相似,就是在方法名前面加一個@XXX註解來為這個方法裝飾一些東西。但是,Java/C#的Annotation也很讓人望而卻步,太TMD的複雜了,你要玩它,你需要了解一堆Annotation的類庫文件,讓人感覺就是在學另外一門語言。
而Python使用了一種相對於Decorator Pattern和Annotation來說非常優雅的方法,這種方法不需要你去掌握什麼複雜的OO模型或是Annotation的各種類庫規定,完全就是語言層面的玩法:一種函數語言程式設計的技巧。如果你看過本站的《
Hello World
下面是程式碼:
123456789101112 | def hello(fn): def wrapper(): print "hello, %s" % fn.__name__ fn() print "goodby, %s" % fn.__name__ return wrapper @hello def foo(): print "i am foo" foo() |
當你執行程式碼,你會看到如下輸出:
1234 | [[email protected]]$ python hello.py hello, foo i am foo goodby, foo |
你可以看到如下的東西:
1)函式foo前面有個@hello的“註解”,hello就是我們前面定義的函式hello
2)在hello函式中,其需要一個fn的引數(這就用來做回撥的函式)
3)hello函式中返回了一個inner函式wrapper,這個wrapper函式回調了傳進來的fn,並在回撥前後加了兩條語句。
Decorator 的本質
對於Python的這個@註解語法糖- Syntactic Sugar 來說,當你在用某個@decorator來修飾某個函式func時,如下所示:
123 | @decorator def func(): pass |
其直譯器會解釋成下面這樣的語句:
1 | func = decorator(func) |
尼瑪,這不就是把一個函式當引數傳到另一個函式中,然後再回調嗎?是的,但是,我們需要注意,那裡還有一個賦值語句,把decorator這個函式的返回值賦值回了原來的func。 根據《函數語言程式設計》中的first class functions中的定義的,你可以把函式當成變數來使用,所以,decorator必需得返回了一個函數出來給func,這就是所謂的higher order function 高階函式,不然,後面當func()呼叫的時候就會出錯。 就我們上面那個hello.py裡的例子來說,
123 | @hello def foo(): print "i am foo" |
被解釋成了:
1 | foo = hello(foo) |
是的,這是一條語句,而且還被執行了。你如果不信的話,你可以寫這樣的程式來試試看:
123456 | def fuck(fn): print "fuck %s!" % fn.__name__[:: - 1 ].upper() @fuck def wfg(): pass |
沒了,就上面這段程式碼,沒有呼叫wfg()的語句,你會發現, fuck函式被呼叫了,而且還很NB地輸出了我們每個人的心聲!
再回到我們hello.py的那個例子,我們可以看到,hello(foo)返回了wrapper()函式,所以,foo其實變成了wrapper的一個變數,而後面的foo()執行其實變成了wrapper()。
知道這點本質,當你看到有多個decorator或是帶引數的decorator,你也就不會害怕了。
比如:多個decorator
1234 | @decorator_one @decorator_two def func(): pass |
相當於:
1 | func = decorator_one(decorator_two(func)) |
比如:帶引數的decorator:
123 | @decorator (arg1, arg2)
|