裝飾器是可調用的對象,其參數是另外一個函數(被裝飾的函數)。html
首先看一下這段代碼python
def deco(fn):
print "I am %s!" % fn.__name__
@deco
def func():
pass
# output
I am func!
# 沒有執行func 函數 可是 deco 被執行了複製代碼
HUGOMORE42git
在用某個@decorator來修飾某個函數func時github
@decorator
def func():
pass複製代碼
其解釋器會解釋成下面這樣的語句:shell
func = decorator(func)
編程
其實就是把一個函數當參數傳到另外一個函數中,而後再回調,可是值得注意的是裝飾器必須返回一個函數給funcflask
裝飾器的一大特性是,能把被裝飾的函數替換成其餘函數。第二大特性是,裝飾器在加載模塊時當即執行。設計模式
裝飾器的一個關鍵特性是,它們在被裝飾的函數定義後當即運行。這一般在導入是(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 時,傳給它的參數是被裝飾的函數(例如
看完上邊的示例咱們知道,函數被裝飾器裝飾後會變成裝飾器函數的一個參數,那這時就不得不說變量的做用域了。
先看下下邊這段代碼:
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
的綁定。
閉包能夠認爲是一個內層函數(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複製代碼
這裏裝飾器生成函數內部傳遞參數是閉包的特性
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__()複製代碼
咱們能夠看到這個類中有兩個成員:
若是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複製代碼
最後,感謝女友支持。
歡迎關注(April_Louisa) | 請我喝芬達 |
---|---|
![]() |
![]() |