原文地址html
以前用python簡單寫了一下斐波那契數列的遞歸實現(以下),發現運行速度很慢。python
def fib_direct(n): assert n > 0, 'invalid n' if n < 3: return 1 else: return fib_direct(n - 1) + fib_direct(n - 2)
而後大體分析了一下fib_direct(5)的遞歸調用過程,以下圖:git
能夠看到屢次重複調用,所以效率十分低。進一步,能夠算出遞歸算法的時間複雜度
。T(n) = T(n-1) + T(n-2),用常係數線性齊次遞推方程的解法,解出遞推方程的特徵根,特徵根裏最大的n次方就是它的時間複雜度O(1.618^n),指數級增加。github
爲了不重複調用,能夠適當地作緩存,python的裝飾器能夠完美的完成這一任務。算法
<!-- more -->編程
python中一切都是對象,這裏須要強調函數是對象
。爲了更好地理解函數也是對象,下面結合代碼片斷來講明這一點。設計模式
def shout(word="yes"): return word.capitalize() + "!" print shout() # outputs: Yes! """ As an object, you can assign the function to a variable like any other object. Notice we don't use parentheses: we are not calling the function, we are putting the function "shout" into the variable "scream". """ scream = shout print scream() # outputs: Yes! """ More than that, it means you can remove the old name 'shout', and the function will still be accessible from 'scream'. """ del shout try: print shout() except NameError, e: print e # outputs: name 'shout' is not defined print scream() # outputs: 'Yes!'
由於函數是對象,因此python中函數還有一個有趣的特性:函數能夠被定義在另外一個函數中
。下面來看一個簡單的例子。api
def talk(): # You can define a function on the fly in "talk" def whisper(word="yes"): return word.lower()+"..." print whisper() """ You call "talk", that defines "whisper" EVERY TIME you call it, then "whisper" is called in "talk". """ talk() # outputs: yes... # But "whisper" DOES NOT EXIST outside "talk". try: print whisper() except NameError, e: print e # outputs : name 'whisper' is not defined
前面已經知道函數是對象。那麼:緩存
能夠被賦給另外一個變量app
能夠被定義在另外一個函數裏
這也意味着,一個函數能夠返回另外一個函數
,下面看一個簡單的例子。
def get_talk(kind="shout"): def whisper(word="yes"): return word.lower() + "..." def shout(word="yes"): return word.capitalize() + "!" return whisper if kind == "whisper" else shout # Get the function and assign it to a variable talk = get_talk() # You can see that "talk" is here a function object: print talk # outputs : <function shout at 0x107ae9578> print talk() # outputs : Yes! # And you can even use it directly if you feel wild: print get_talk("whisper")() # outputs : yes...
咱們來進一步挖掘一下函數的特性,既然能夠返回函數
,那麼咱們也能夠把函數做爲參數傳遞
。
def whisper(word="yes"): return word.lower() + "..." def do_something_before(func): print "I do something before." print "Now the function you gave me:\n", func() do_something_before(whisper) """outputs I do something before. Now the function you gave me: yes... """
如今,瞭解裝飾器所須要的全部要點咱們已經掌握了,經過上面的例子,咱們還能夠看出,裝飾器其實就是封裝器
,可讓咱們在不修改原函數的基礎上,在執行原函數的先後執行別的代碼。
下面咱們手工實現一個簡單的裝飾器。
def my_shiny_new_decorator(a_function_to_decorate): """ Inside, the decorator defines a function on the fly: the wrapper. This function is going to be wrapped around the original function so it can execute code before and after it. """ def the_wrapper_around_the_original_function(): """ Put here the code you want to be executed BEFORE the original function is called """ print "Before the function runs" # Call the function here (using parentheses) a_function_to_decorate() """ Put here the code you want to be executed AFTER the original function is called """ print "After the function runs" """ At this point, "a_function_to_decorate" HAS NEVER BEEN EXECUTED. We return the wrapper function we have just created. The wrapper contains the function and the code to execute before and after. It’s ready to use! """ return the_wrapper_around_the_original_function # Now imagine you create a function you don't want to ever touch again. def a_stand_alone_function(): print "I am a stand alone function, don't you dare modify me" a_stand_alone_function() # outputs: I am a stand alone function, don't you dare modify me """ Well, you can decorate it to extend its behavior. Just pass it to the decorator, it will wrap it dynamically in any code you want and return you a new function ready to be used: """ a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function) a_stand_alone_function_decorated() """outputs: Before the function runs I am a stand alone function, don't you dare modify me After the function runs """
如今,若是咱們想每次調用a_stand_alone_function
的時候,實際上調用的是封裝後的函數a_stand_alone_function_decorated
,那麼只須要用a_stand_alone_function去覆蓋my_shiny_new_decorator返回的函數便可。也就是:
a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
對於前面的例子,若是用裝飾器語法,能夠添加以下:
@my_shiny_new_decorator def another_stand_alone_function(): print "Leave me alone" another_stand_alone_function() """outputs: Before the function runs Leave me alone After the function runs """
對了,這就是裝飾器語法,這裏的@my_shiny_new_decorator
是another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)
的簡寫。
裝飾器只是裝飾器設計模式的python實現,python還存在其餘幾個經典的設計模式,以方便開發,例如迭代器iterators。
固然了,咱們也能夠嵌套裝飾器。
def bread(func): def wrapper(): print "</''''''\>" func() print "<\______/>" return wrapper def ingredients(func): def wrapper(): print "#tomatoes#" func() print "~salad~" return wrapper def sandwich(food="--ham--"): print food sandwich() # outputs: --ham-- sandwich = bread(ingredients(sandwich)) sandwich() """outputs: </''''''\> #tomatoes# --ham-- ~salad~ <\______/> """
用python的裝飾器語法,以下:
@bread @ingredients def sandwich_2(food="--ham_2--"): print food sandwich_2()
放置裝飾器的位置很關鍵。
@ingredients @bread def strange_sandwich(food="--ham--"): print food strange_sandwich() """outputs: #tomatoes# </''''''\> --ham-- <\______/> ~salad~ """
當咱們調用裝飾器返回的函數時,實際上是在調用封裝函數,給封裝函數傳遞參數也就一樣的給被裝飾函數傳遞了參數。
def a_decorator_passing_arguments(function_to_decorate): def a_wrapper_accepting_arguments(arg1, arg2): print "I got args! Look:", arg1, arg2 function_to_decorate(arg1, arg2) return a_wrapper_accepting_arguments """ Since when you are calling the function returned by the decorator, you are calling the wrapper, passing arguments to the wrapper will let it pass them to the decorated function """ @a_decorator_passing_arguments def print_full_name(first_name, last_name): print "My name is", first_name, last_name print_full_name("Peter", "Venkman") """outputs: I got args! Look: Peter Venkman My name is Peter Venkman """
python中函數和方法幾乎同樣,除了方法中第一個參數是指向當前對象的引用(self)。這意味着咱們能夠爲方法建立裝飾器,只是要記得考慮self。
def method_friendly_decorator(method_to_decorate): def wrapper(self, lie): lie = lie - 3 return method_to_decorate(self, lie) return wrapper class Lucy(object): def __init__(self): self.age = 32 @method_friendly_decorator def sayYourAge(self, lie): print "I am %s, what did you think?" % (self.age + lie) l = Lucy() l.sayYourAge(-3) # outputs: I am 26, what did you think?
咱們還能夠建立一個通用的裝飾器,能夠用於全部的方法或者函數,並且不用考慮它的參數狀況。這時候,咱們要用到*args, **kwargs
。
def a_decorator_passing_arbitrary_arguments(function_to_decorate): # The wrapper accepts any arguments def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs): print "Do I have args?:" print args print kwargs # Then you unpack the arguments, here *args, **kwargs # If you are not familiar with unpacking, check: # http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/ function_to_decorate(*args, **kwargs) return a_wrapper_accepting_arbitrary_arguments
另外還有一些高級用法,這裏不作詳細說明,能夠在How can I make a chain of function decorators in Python?進一步深刻了解裝飾器。
裝飾器封裝了函數,這使得調試函數變得困難。不過在python 2.5引入了functools
模塊,它包含了functools.wraps()
函數,這個函數能夠將被封裝函數的名稱、模塊、文檔拷貝給封裝函數。有趣的是,functools.wraps是一個裝飾器。爲了更好地理解,看如下代碼:
# For debugging, the stacktrace prints you the function __name__ def foo(): print "foo" print foo.__name__ # outputs: foo def bar(func): def wrapper(): print "bar" return func() return wrapper @bar def foo(): print "foo" print foo.__name__ # outputs: wrapper import functools def bar(func): # We say that "wrapper", is wrapping "func" # and the magic begins @functools.wraps(func) def wrapper(): print "bar" return func() return wrapper @bar def foo(): print "foo" print foo.__name__ # outputs: foo
讓咱們回到本篇文章開始的問題上,重複調用致使遞歸的效率低下,所以考慮使用緩存機制,空間換時間。這裏,就可使用裝飾器作緩存,看下面代碼:
from functools import wraps def cache(func): caches = {} @wraps(func) def wrap(*args): if args not in caches: caches[args] = func(*args) return caches[args] return wrap @cache def fib_cache(n): assert n > 0, 'invalid n' if n < 3: return 1 else: return fib_cache(n - 1) + fib_cache(n - 2)
這樣遞歸中就不會重複調用,效率也會提升不少。具體能夠看這裏,從執行時間很容易看出作了緩存以後速度有了很大的提高。裝飾器還能夠用來擴展外部接口函數(一般你不能修改它)的功能,或者用來調試函數。其實,裝飾器能夠用於各類各樣的場合!
python自己提供了一些裝飾器:property,staticmethod,等等。另外,Django使用裝飾器去管理緩存和權限。
計算斐波納契數,分析算法複雜度
How can I make a chain of function decorators in Python?
Python裝飾器與面向切面編程
how to use args and kwargs in python?
Fibonacci, recursion and decorators