天天看點

python中裝飾器_Python裝飾器入門

Python允許你,作為程式員,使用函數完成一些很酷的事情。在Python中,函數是

>>> def square(n):

... return n * n;

>>> square(4)

16

>>> alias = square

>>> alias(4)

16

然而,一等函數的真正威力在于你可以把函數傳給其他函數,或者從其他函數中傳回函數。Python的内置函數map利用了這種能力:給map傳個函數以及一個清單,它會依次以清單中每個元素為參數調用你傳給它的那個函數,進而生成一個新的清單。如下所示的例子中應用了上面的那個square函數:

>>> number = [1, 2, 3, 4, 5]

>>> map(square, numbers)

[1, 4, 9, 16, 25]

如果一個函數接受其他函數作為參數,以及/或者傳回一個函數,那麼它就被稱為

例如,假設有這樣一個函數,會被調用很多次,以緻運作代價非常昂貴:

>>> def fib(n):

... "Recursively (i.e., dreadfully) calculate the nth Fibonacci number."

... return n if n in [0, 1] else fib(n - 2) + fib(n - 1)

我們一般會儲存計算過程中每次遞歸調用的結果,這樣,對于函數調用樹中經常出現某個n,當需要計算n對應的結果時,就不需要重複計算了。有多種方式可以做到這點。例如,我們可以将這些結果存在一個字典中,當以某個值為參數調用fib函數時,就先到這個字典去查一下其結果是否已經計算出來了。

但這樣的話,每次我們想要調用fib函數,都需要重複那段相同的字典檢查樣闆式代碼。相反,如果讓fib函數自己在内部負責存儲其結果,那麼在其他代碼中調用fib,就非常友善,隻要簡單地調用它就行了。這樣一種技術被稱為

我們可以把這種memoization代碼直接放入fib函數,但是Python為我們提供了另外一種更加優雅的選擇。因為可以編寫修改其他函數的函數,那麼我們可以編寫一個通用的memoization函數,以一個函數作為參數,并傳回這個函數的memoization版本:

def memoize(fn):

stored_results = {}

def memoized(*args):

try:

# try to get the cached result

return stored_results[args]

except KeyError:

# nothing was cached for those args. let's fix that.

result = stored_results[args] = fn(*args)

return result

return memoized

如上, memoize 函數以另一個函數作為參數,函數體中建立了一個字典對象用來存儲函數調用的結果:鍵為被memoized包裝後的函數的參數,值為以鍵為參數調用函數的傳回值。 memoize 函數傳回一個新的函數,這個函數會首先檢查在 stored_results 字典中是否存在與目前參數對應的條目;如果有,對應的存儲值會被傳回;否則,就調用經過包裝的函數,存儲其傳回值,并且傳回給調用者。memoize傳回的這種新函數常被稱為"包裝器"函數,因為它隻是另外一個真正起作用的函數外面的一個薄層。

很好,現在有了一個memoization函數,我們可以把fib函數傳給它,進而得到一個經過包裝的fib,這個版本的fib函數不需要重複以前那樣的繁重工作:

def fib(n):

return n if n in [0, 1] else fib(n - 2) + fib(n - 1)

fib = memoize(fib)

通過高階函數memoize,我們獲得了memoization帶來的好處,并且不需要對fib函數自己做出任何改變,以免夾雜着memoization的代碼而模糊了函數的實質工作。但是,你也許注意到上面的代碼還算有點别扭,因為我們必須寫3遍fib。由于這種模式-傳遞一個函數給另一個函數,然後将結果傳回給與原來那個函數同名的函數變量-在使用包裝器函數的代碼中極為常見,Python為其提供了一種特殊的文法:裝飾器:

@memoize

def fib(n):

return n if n in [0, 1] else fib(n - 2) + fib(n -1)

這裡,我們說memoize函數裝飾了fib函數。需要注意的是這僅是一種文法上的簡便寫法(譯注:就是我們常說的"文法糖")。這段代碼與前面的代碼片段做的是同樣的事情:定義一個名為fib的函數,把它傳給memoize函數,将傳回結果存為名為fib的函數變量。特殊的(看起來有點奇怪的)@文法隻是減少了備援。

你可以将多個裝飾器堆疊起來使用,它們會自底向上地逐個起作用。例如,假設我們還有另一個用來幫助調試的高階函數:

def make_verbose(fn):

def verbose(*args):

# will print (e.g.) fib(5)

print '%s(%s)' % (fb.__name__, ', '.join(repr(arg) for arg in args))

return fn(*args) # actually call the decorated function

return verbose

下面的兩個代碼片段做的是同樣的事情:

@memoize

@make_verbose

def fib(n):

return n if n in [0, 1] else fib(n - 2) + fib(n - 1)

def fib(n):

return n if n in [0, 1] else fib(n - 2) + fib(n - 1)

fib = memoize(make_verbose(fib))

有趣的是,Python并沒有限制你在@符号後隻能寫一個函數名:你也可以調用一個函數,進而能夠高效地傳遞參數給裝飾器。假設我們并不滿足于簡單的memoization,還想将函數的結果存儲到memcached 裝飾器函數,那麼可以(例如)傳遞一個伺服器位址給它:

@memcached('127.0.0.1:11211')

def fib(n):

return n if n in [0, 1] else fib(n - 2) + fib(n - 1)

非裝飾器文法的寫法會如下展開:

fib = memcached('127.0.0.1:11211')(fib)

Python配備有一些作為裝飾器使用的非常有用的函數。例如,Python有一個 classmethod 函數,可以建立大緻類似于java的靜态方法:

class Foo(object):

SOME_CLASS_CONSTANT = 42

@classmethod

def add_to_my_constant(cls, value):

# Here, `cls` will just be Foo, buf if you called this method on a

# subclass of Foo, `cls` would be that subclass instead.

return cls.SOME_CLASS_CONSTANT + value

Foo.add_to_my_constant(10) # => 52

# unlike in Java, you can also call a classmethod on an instance

f = Foo()

f.add_to_my_constant(10) # => 52

旁注:文檔字元串

Python函數可以包含更多的資訊,而不僅僅是代碼:它們也包含有用的幫助資訊,比如函數名稱,文檔字元串:

>>> def fib(n):

... "Recursively (i.e., dreadfully) calculate the nth Fibonacci number."

... return n if n in [0, 1] else fib(n - 2) + fib(n - 1)

...

>>> fib.__name__

'fib'

>>> fib.__doc__

'Recursively (i.e., dreadfully) calculate the nth Fibonacci number.'

Python内置函數

>>> fib = memoized(fib)

>>> fib.__name__

'memoized'

>>> fib.__doc__

那樣的資訊并沒有什麼用處。幸運的是,Python包含一個名為 functools.wraps 的助手函數,能夠把函數的幫助資訊拷貝到其包裝器函數:

import functools

def memoize(fn):

stored_results = {}

@functools.wraps(fn)

def memoized(*args):

# (as before)

return memoized

使用裝飾器幫助你編寫裝飾器會使很多事情令人非常滿意。現在,如果使用更新過的memoize函數重試前面的代碼,我們将會看到得到保留的文檔:

>>> fib = memoized(fib)

>>> fib.__name__

'fib'

>>> fib.__doc__

'Recursively (i.e., dreadfully) calculate the nth Fibonacci number.'