裝飾器是可調用的對象,其參數是另外一個函數(被裝飾的函數)。html
首先看一下這段代碼python
def deco(fn): print "I am %s!" % fn.__name__ @deco def func(): pass # output I am func! # 沒有執行func 函數 可是 deco 被執行了
在用某個@decorator來修飾某個函數func時git
@decorator def func(): pass
其解釋器會解釋成下面這樣的語句:github
func = decorator(func)
shell
其實就是把一個函數當參數傳到另外一個函數中,而後再回調,可是值得注意的是裝飾器必須返回一個函數給func編程
裝飾器的一大特性是,能把被裝飾的函數替換成其餘函數。第二大特性是,裝飾器在加載模塊時當即執行。flask
裝飾器的一個關鍵特性是,它們在被裝飾的函數定義後當即運行。這一般在導入是(python 加載模塊時)。設計模式
看下下面的示例:數組
registry = [] # registry 保存被@register 裝飾的函數的引用 def register(func): # register 的參數是一個函數 print('running register(%s)' % func) # 打印被裝飾的函數 registry.append(func) # 把 func 存入 `registery` return func # 返回 func:必須返回函數,這裏返回的函數與經過參數傳入的同樣 @register # `f1` 和 `f2`被 `@register` 裝飾 def f1(): print('running f1()') @register def f2(): print('running f2()') def f3(): # <7> print('running f3()') def main(): # main 打印 `registry`,而後調用 f1()、f2()和 f3() print('running main()') print('registry ->', registry) f1() f2() f3() if __name__=='__main__': main() # <9>
運行代碼結果以下:緩存
running register(<function f1 at 0x1023fb378>) running register(<function f2 at 0x1023fb400>) running main() registry -> [<function f1 at 0x1023fb378>, <function f2 at 0x1023fb400>] running f1() running f2() running f3()
從結果能夠發現register
在模塊中其餘函數以前運行了兩次。調用 register 時,傳給它的參數是被裝飾的函數(例如<function f1 at 0x1023fb378>)。
看完上邊的示例咱們知道,函數被裝飾器裝飾後會變成裝飾器函數的一個參數,那這時就不得不說變量的做用域了。
先看下下邊這段代碼:
def f1(a): print(locals()) print(a) print(b) f1(3) # output {'a': 3} 3 Traceback(most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in f1 NameError: global name 'b' is not defined
這裏的錯誤是由於全局變量 b
沒有定義,若是咱們先在函數外部給 b 賦值,再調用這個方法就不會報錯了。
函數運行時會建立一個新的做用域(命名空間)。函數的命名空間隨着函數調用開始而開始,結束而銷燬。
這個例子中 f1 的命名空間中只有 {'a': 3},因此b
會被認爲是全局變量。
再看一個例子:
b = 6 def f2(a): print(a) print(globals()) print(locals()) print(b) b = 9 f2(3) # output 3 { '__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x10c7f2dd8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '~/var_local.py', '__cached__': None, 'b': 6, 'f2': <function f2 at 0x10c7e7598> } {'a': 3} 3 Traceback(most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in f1 UnboundLocalError: local variable 'b' referenced before assignment
這個例子和上一個例子不一樣是,我如今函數外部定義了全局變量b
,可是執行f2
這個方法並無打印6,這是爲何呢?
這是由於執行函數時 Python 會嘗試從局部變量中獲取 b
,函數對於已經引用但未賦值的變量並不會自動聲明爲局部變量,因此解釋器發現後邊的賦值以前有引用就會拋出 UnboundLocalError
錯誤。
Python 不要求聲明變量,可是假定在函數定義體中賦值的變量是局部變量。
若是要讓解釋器把b
當作全局變量,要使用global
聲明:
b = 6 def f3(a): global b print(a) print(b) b = 9 f2(3) # output 3 6
閉包
是一種函數,它會保留定義函數時存在的自由變量的綁定,這樣調用函數時,雖然定義做用域不可用,但仍能使用那些綁定。
介紹閉包前先要說明一下 Python 的函數參數
函數有兩種參數
位置參數
命名參數
def foo(x, y=0): return x - y
函數和python中其餘同樣都是對象
In [7]: class A(object): ...: pass In [8]: A Out[8]: __main__.A In [9]: type(A) Out[9]: type In [10]: def foo(): ....: pass In [11]: type(foo) Out[11]: function In [12]: A.__class__ Out[12]: type In [13]: foo.__class__ Out[13]: function In [14]: a = 1 In [15]: a.__class__ Out[15]: int # 類 是對象 In [16]: issubclass(A.__class__, object) Out[16]: True # 變量 是對象 In [17]: issubclass(a.__class__, object) Out[17]: True # 函數 是對象 In [18]: issubclass(foo.__class__, object) Out[18]: True
因此函數也能夠做爲參數傳遞給其它函數,也能夠被當作返回值返回
def add(x, y): return x + y def apply(func): return func >> a = apply(add) >> type(a) <type 'function'> >> a(1, 2) >> 3
先來看一個示例:假設有個名爲 avg 的函數,它的做用是計算不斷增長的系列值的均值;
它是這麼使用的:
>>> avg(10) 10 >>> avg(11) 10.5 >>> avg(12) 11
那麼咱們考慮下,avg 從何而來,它又在哪裏保存歷史值呢,這個用閉包如何實現呢?
下邊的代碼是閉包的實現:
def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total/len(series) return averager
調用 make_averager
時,返回一個 averager 函數對象。每次調用 averager 時,它都會把參數添加到系列值中,而後計算當前平均值。
avg = make_averager() >>> avg(10) 10 >>> avg(11) 10.5 >>> avg(12) 11
series
是make_averager
函數的局部變量,由於那個函數的定義體中初始化了series: series=[]
。但在averager
函數中,series
是自由變量(指未在本地做用域中綁定的變量)。
averager
的閉包延伸到那個函數的做用域以外,包含自由變量series
的綁定。
avg 就是一個閉包
也能夠說 make_averager 指向一個閉包
或者說 make_averager 是閉包的工廠函數
閉包能夠認爲是一個內層函數(averager),由一個變量指代,而這個變量相對於外層包含它的函數而言,是本地變量
嵌套定義在非全局做用域裏面的函數可以記住它在被定義的時候它所處的封閉命名空間
閉包
只是在形式和表現上像函數,但實際上不是函數。函數是一些可執行的代碼,這些代碼在函數被定義後就肯定了,不會在執行時發生變化,因此一個函數只有一個實例。閉包在運行時能夠有多個實例,不一樣的引用環境和相同的函數組合能夠產生不一樣的實例。
對一個已有的模塊作一些「修飾工做」,所謂修飾工做就是想給現有的模塊加上一些小裝飾(一些小功能,這些小功能可能好多模塊都會用到),但又不讓這個小裝飾(小功能)侵入到原有的模塊中的代碼裏去
def my_decorator(func): def wrapper(): print "Before the function runs" func() # 這行代碼可用,是由於 wrapper 的閉包中包含自由變量 func print "After the function runs" return wrapper def my_func(): print "I am a stand alone function" >> my_func() # output I am a stand alone function # 而後,咱們在這裏裝飾這個函數 # 將函數傳遞給裝飾器,裝飾器將動態地將其包裝在任何想執行的代碼中,而後返回一個新的函數 >> my_func = my_decorator(my_func) >> my_func() #output Before the function runs I am a stand alone function After the function runs # 也能夠這麼寫 @ my_decorator def my_func(): print "I am a stand alone function" >> my_func() #output Before the function runs I am a stand alone function After the function runs
裝飾器能夠嵌套使用
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 #### outputs:
>> sandwich = bread(ingredients(sandwich)) >> sandwich() #### outputs </''''''\> #tomatoes# --ham-- ~salad~ <\______/>
更簡單的寫法
@bread @ingredients def sandwich(food="--ham--"): print food
裝飾器的順序是很重要的
若是咱們換下順序就會發現,三明治變成了披薩。。
@ingredients @bread def sandwich(food="--ham--"): print food # outputs: #tomatoes# </' ' ' ' ' '\> --ham-- <\______/> ~salad~
首先看一下這段代碼
def deco(fn): print "I am %s!" % fn.__name__ @deco def func(): pass # output I am func! # 沒有執行func 函數 可是 deco 被執行了
在用某個@decorator來修飾某個函數func時
@decorator def func(): pass
其解釋器會解釋成下面這樣的語句:
func = decorator(func)
其實就是把一個函數當參數傳到另外一個函數中,而後再回調
可是值得注意的是裝飾器必須返回一個函數給func
回到剛纔的例子
def my_decorator(func): def wrapper(): print "Before the function runs" func() print "After the function runs" return wrapper def my_func(): print "I am a stand alone function" >> my_func = my_decorator(my_func) >> my_func() #output Before the function runs I am a stand alone function After the function runs
my_decorator(my_func)返回了wrapper()函數,因此,my_func其實變成了wrapper的一個變量,然後面的my_func()執行其實變成了wrapper()
好比:多個decorator
@decorator_one @decorator_two def func(): pass
至關於:
func = decorator_one(decorator_two(func))
好比:帶參數的decorator:
@decorator(arg1, arg2) def func(): pass # 至關於: func = decorator(arg1,arg2)(func)
首先看一下, 若是被裝飾的方法有參數
def a_decorator(method_to_decorate): def wrapper(self, x): x -= 3 print 'x is %s' % x method_to_decorate(self, x) return wrapper class A(object): def __init__(self): self.b = 42 @a_decorator def number(self, x): print "b is %s" % (self.b + x) a = A() a.number(-3) # output x is -6 b is 36
一般咱們都使用更加通用的裝飾器,能夠做用在任何函數或對象方法上,而沒必要關心其參數使用
def a_decorator(method_to_decorate): def wrapper(*args, **kwargs): print '****** args ******' print args print kwargs method_to_decorate(*args, **kwargs) return wrapper @a_decorator def func(): pass func() #output ****** args ****** () {} @a_decorator def func_with_args(a, b=0): pass return a + b func_with_args(1, b=2) #output ****** args ****** (1,) {'b': 2}
上邊的示例是帶參數的被裝飾函數
如今咱們看一下向裝飾器自己傳遞參數
裝飾器必須使用函數做爲參數,你不能直接傳遞參數給裝飾器自己
若是想傳遞參數給裝飾器,能夠 聲明一個用於建立裝飾器的函數
# 我是一個建立裝飾器的函數 def decorator_maker(): print "I make decorators!" def my_decorator(func): print "I am a decorator!" def wrapped(): print "I am the wrapper around the decorated function. " return func() print "As the decorator, I return the wrapped function." return wrapped print "As a decorator maker, I return a decorator" return my_decorator # decorator_maker()返回的是一個裝飾器 new_deco = decorator_maker() #outputs I make decorators! As a decorator maker, I return a decorator # 使用裝飾器 def decorated_function(): print "I am the decorated function" decorated_function = new_deco(decorated_function) decorated_function() # outputs I make decorators! As a decorator maker, I return a decorator I am a decorator! As the decorator, I return the wrapped function. I am the wrapper around the decorated function. I am the decorated function
使用@修飾
decorated_function = new_deco(decorated_function) # 等價於下面的方法 @new_deco def func(): print "I am the decorated function" @decorator_maker() def func(): print "I am the decorated function"
my_decorator(裝飾器函數)是decorator_maker(裝飾器生成函數)的內部函數
因此可使用把參數加在decorator_maker(裝飾器生成函數)的方法像裝飾器傳遞參數
# 我是一個建立帶參數裝飾器的函數 def decorator_maker_with_arguments(darg1, darg2): print "I make decorators! And I accept arguments:", darg1, darg2 def my_decorator(func): print "I am a decorator! Somehow you passed me arguments:", darg1, darg2 def wrapped(farg1, farg2): print "I am the wrapper around the decorated function." print "I can access all the variables", darg1, darg2, farg1, farg2 return func(farg1, farg2) print "As the decorator, I return the wrapped function." return wrapped print "As a decorator maker, I return a decorator" return my_decorator @decorator_maker_with_arguments("deco_arg1", "deco_arg2") def decorated_function_with_arguments(function_arg1, function_arg2): print ("I am the decorated function and only knows about my arguments: {0}" " {1}".format(function_arg1, function_arg2)) decorated_function_with_arguments('farg1', 'farg2') # outputs I make decorators! And I accept arguments: deco_arg1 deco_arg2 As a decorator maker, I return a decorator I am a decorator! Somehow you passed me arguments: deco_arg1 deco_arg2 As the decorator, I return the wrapped function. I am the wrapper around the decorated function. I can access all the variables deco_arg1 deco_arg2 farg1 farg2 I am the decorated function and only knows about my arguments: farg1 farg2
這裏裝飾器生成函數內部傳遞參數是閉包的特性
裝飾器是Python2.4的新特性
裝飾器會下降代碼的性能
裝飾器僅在Python代碼導入時被調用一次,以後你不能動態地改變參數.當你使用"import x",函數已經被裝飾
functools.wraps
最後Python2.5解決了最後一個問題,它提供functools
模塊,包含functools.wraps
,這個函數會將被裝飾函數的名稱、模塊、文檔字符串拷貝給封裝函數
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
"functools" 能夠修正這個錯誤
import functools def bar(func): # 咱們所說的 "wrapper", 封裝 "func" @functools.wraps(func) def wrapper(): print "bar" return func() return wrapper @bar def foo(): print "foo" # 獲得的是原始的名稱, 而不是封裝器的名稱 print foo.__name__ #outputs: foo
class myDecorator(object): def __init__(self, func): print "inside myDecorator.__init__()" self.func = func def __call__(self): self.func() print "inside myDecorator.__call__()" @myDecorator def aFunction(): print "inside aFunction()" print "Finished decorating aFunction()" aFunction() # output: # inside myDecorator.__init__() # Finished decorating aFunction() # inside aFunction() # inside myDecorator.__call__()
咱們能夠看到這個類中有兩個成員:
一個是__init__(),這個方法是在咱們給某個函數decorator時被調用,因此,須要有一個func的參數,也就是被decorator的函數。
一個是__call__(),這個方法是在咱們調用被decorator函數時被調用的
若是decorator有參數的話,__init__() 就不能傳入func了,而fn是在__call__的時候傳入
class myDecorator(object): def __init__(self, arg1, arg2): self.arg1 = arg2 def __call__(self, func): def wrapped(*args, **kwargs): return self.func(*args, **kwargs) return wrapped
Python 內置了三個用於裝飾方法的函數:property、classmethod和 staticmethod。
另外一個常見的裝飾器是 functools.wraps,它的做用是協助構建行爲良好的裝飾器。
functools.lru_cache
實現了內存緩存功能,它能夠把耗時長的函數結果保存起來,避免傳入相同參數時重複計算。
咱們本身的實現代碼以下:
from functools import wraps def memo(fn): cache = {} miss = object() @wraps(fn) def wrapper(*args): result = cache.get(args, miss) if result is miss: result = fn(*args) print "{0} has been used: {1}x".format(fn.__name__, wrapper.count) cache[args] = result return result return wrapper @memo def fib(n): if n < 2: return n return fib(n - 1) + fib(n - 2)
def counter(func): """ 記錄並打印一個函數的執行次數 """ def wrapper(*args, **kwargs): wrapper.count = wrapper.count + 1 res = func(*args, **kwargs) print "{0} has been used: {1}x".format(func.__name__, wrapper.count) return res wrapper.count = 0 return wrapper
裝飾器作緩存
def cache_for(duration): def deco(func): @wraps(func) def fn(*args, **kwargs): key = pickle.dumps((args, kwargs)) value, expire = func.func_dict.get(key, (None, None)) now = int(time.time()) if value is not None and expire > now: return value value = func(*args, **kwargs) func.func_dict[key] = (value, int(time.time()) + duration) return value return fn return deco
def timeit(fn): @wraps(fn) def real_fn(*args, **kwargs): if config.common['ENVIRON'] == 'PRODUCTION': return fn(*args, **kwargs) _start = time.time() #app.logger.debug('Start timeit for %s' % fn.__name__) result = fn(*args, **kwargs) _end = time.time() _last = _end - _start app.logger.debug('End timeit for %s in %s seconds.' % (fn.__name__, _last)) return result return real_fn
[args and *kwargs? [duplicate]](http://stackoverflow.com/ques...
最後,感謝女友支持。
歡迎關注(April_Louisa) | 請我喝芬達 |
---|---|
![]() |
![]() |