摘要:裝飾器是可調(diào)用的對象,其參數(shù)是另一個函數(shù)被裝飾的函數(shù)。第二大特性是,裝飾器在加載模塊時立即執(zhí)行。另一個常見的裝飾器是,它的作用是協(xié)助構(gòu)建行為良好的裝飾器。
裝飾器基礎(chǔ)知識裝飾器是可調(diào)用的對象,其參數(shù)是另一個函數(shù)(被裝飾的函數(shù))。
首先看一下這段代碼
def deco(fn):
print "I am %s!" % fn.__name__
@deco
def func():
pass
# output
I am func!
# 沒有執(zhí)行func 函數(shù) 但是 deco 被執(zhí)行了
在用某個@decorator來修飾某個函數(shù)func時
@decorator
def func():
pass
其解釋器會解釋成下面這樣的語句:
func = decorator(func)
其實就是把一個函數(shù)當參數(shù)傳到另一個函數(shù)中,然后再回調(diào),但是值得注意的是裝飾器必須返回一個函數(shù)給func
裝飾器的一大特性是,能把被裝飾的函數(shù)替換成其他函數(shù)。第二大特性是,裝飾器在加載模塊時立即執(zhí)行。
裝飾器何時執(zhí)行裝飾器的一個關(guān)鍵特性是,它們在被裝飾的函數(shù)定義后立即運行。這通常在導入是(python 加載模塊時)。
看下下面的示例:
registry = [] # registry 保存被@register 裝飾的函數(shù)的引用
def register(func): # register 的參數(shù)是一個函數(shù)
print("running register(%s)" % func) # 打印被裝飾的函數(shù)
registry.append(func) # 把 func 存入 `registery`
return func # 返回 func:必須返回函數(shù),這里返回的函數(shù)與通過參數(shù)傳入的一樣
@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`,然后調(diào)用 f1()、f2()和 f3()
print("running main()")
print("registry ->", registry)
f1()
f2()
f3()
if __name__=="__main__":
main() # <9>
運行代碼結(jié)果如下:
running register() running register( ) running main() registry -> [ , ] running f1() running f2() running f3()
從結(jié)果可以發(fā)現(xiàn)register 在模塊中其他函數(shù)之前運行了兩次。調(diào)用 register 時,傳給它的參數(shù)是被裝飾的函數(shù)(例如
看完上邊的示例我們知道,函數(shù)被裝飾器裝飾后會變成裝飾器函數(shù)的一個參數(shù),那這時就不得不說變量的作用域了。
變量作用域先看下下邊這段代碼:
def f1(a):
print(locals())
print(a)
print(b)
f1(3)
# output
{"a": 3}
3
Traceback(most recent call last):
File "", line 1, in
File "", line 3, in f1
NameError: global name "b" is not defined
這里的錯誤是因為全局變量 b 沒有定義,如果我們先在函數(shù)外部給 b 賦值,再調(diào)用這個方法就不會報錯了。
函數(shù)運行時會創(chuàng)建一個新的作用域(命名空間)。函數(shù)的命名空間隨著函數(shù)調(diào)用開始而開始,結(jié)束而銷毀。
這個例子中 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__": ,
"__file__": "~/var_local.py",
"__cached__": None,
"b": 6,
"f2":
}
{"a": 3}
3
Traceback(most recent call last):
File "", line 1, in
File "", line 3, in f1
UnboundLocalError: local variable "b" referenced before assignment
這個例子和上一個例子不同是,我現(xiàn)在函數(shù)外部定義了全局變量b,但是執(zhí)行f2 這個方法并沒有打印6,這是為什么呢?
這是因為執(zhí)行函數(shù)時 Python 會嘗試從局部變量中獲取 b,函數(shù)對于已經(jīng)引用但未賦值的變量并不會自動聲明為局部變量,所以解釋器發(fā)現(xiàn)后邊的賦值之前有引用就會拋出 UnboundLocalError 錯誤。
Python 不要求聲明變量,但是假定在函數(shù)定義體中賦值的變量是局部變量。
如果要讓解釋器把b當做全局變量,要使用global聲明:
b = 6
def f3(a):
global b
print(a)
print(b)
b = 9
f2(3)
# output
3
6
閉包
閉包是一種函數(shù),它會保留定義函數(shù)時存在的自由變量的綁定,這樣調(diào)用函數(shù)時,雖然定義作用域不可用,但仍能使用那些綁定。
介紹閉包前先要說明一下 Python 的函數(shù)參數(shù)
函數(shù)的兩種參數(shù)函數(shù)有兩種參數(shù)
位置參數(shù)
命名參數(shù)
def foo(x, y=0):
return x - y
python 中一切都是對象
函數(shù)和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 # 函數(shù) 是對象 In [18]: issubclass(foo.__class__, object) Out[18]: True
所以函數(shù)也可以作為參數(shù)傳遞給其它函數(shù),也可以被當做返回值返回
def add(x, y):
return x + y
def apply(func):
return func
>> a = apply(add)
>> type(a)
>> a(1, 2)
>> 3
閉包的使用
先來看一個示例:假設有個名為 avg 的函數(shù),它的作用是計算不斷增加的系列值的均值;
它是這么使用的:
>>> avg(10) 10 >>> avg(11) 10.5 >>> avg(12) 11
那么我們考慮下,avg 從何而來,它又在哪里保存歷史值呢,這個用閉包如何實現(xiàn)呢?
下邊的代碼是閉包的實現(xiàn):
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total/len(series)
return averager
調(diào)用 make_averager 時,返回一個 averager 函數(shù)對象。每次調(diào)用 averager 時,它都會把參數(shù)添加到系列值中,然后計算當前平均值。
avg = make_averager() >>> avg(10) 10 >>> avg(11) 10.5 >>> avg(12) 11
series 是make_averager 函數(shù)的局部變量,因為那個函數(shù)的定義體中初始化了series: series=[]。但在averager 函數(shù)中,series 是自由變量(指未在本地作用域中綁定的變量)。
averager 的閉包延伸到那個函數(shù)的作用域之外,包含自由變量series的綁定。
avg 就是一個閉包
也可以說 make_averager 指向一個閉包
或者說 make_averager 是閉包的工廠函數(shù)
裝飾器 實現(xiàn)一個簡單的裝飾器閉包可以認為是一個內(nèi)層函數(shù)(averager),由一個變量指代,而這個變量相對于外層包含它的函數(shù)而言,是本地變量
嵌套定義在非全局作用域里面的函數(shù)能夠記住它在被定義的時候它所處的封閉命名空間閉包 只是在形式和表現(xiàn)上像函數(shù),但實際上不是函數(shù)。函數(shù)是一些可執(zhí)行的代碼,這些代碼在函數(shù)被定義后就確定了,不會在執(zhí)行時發(fā)生變化,所以一個函數(shù)只有一個實例。閉包在運行時可以有多個實例,不同的引用環(huán)境和相同的函數(shù)組合可以產(chǎn)生不同的實例。
對一個已有的模塊做一些“修飾工作”,所謂修飾工作就是想給現(xiàn)有的模塊加上一些小裝飾(一些小功能,這些小功能可能好多模塊都會用到),但又不讓這個小裝飾(小功能)侵入到原有的模塊中的代碼里去
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
# 然后,我們在這里裝飾這個函數(shù)
# 將函數(shù)傳遞給裝飾器,裝飾器將動態(tài)地將其包裝在任何想執(zhí)行的代碼中,然后返回一個新的函數(shù)
>> 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
裝飾器是設計模式中裝飾器模式(英文版)的python實現(xiàn)。
多個裝飾器裝飾器可以嵌套使用
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
裝飾器的順序是很重要的
如果我們換下順序就會發(fā)現(xiàn),三明治變成了披薩。。
@ingredients
@bread
def sandwich(food="--ham--"):
print food
# outputs:
#tomatoes#
" " " " " ">
--ham--
<\______/>
~salad~
Decorator 的工作原理
首先看一下這段代碼
def deco(fn):
print "I am %s!" % fn.__name__
@deco
def func():
pass
# output
I am func!
# 沒有執(zhí)行func 函數(shù) 但是 deco 被執(zhí)行了
在用某個@decorator來修飾某個函數(shù)func時
@decorator
def func():
pass
其解釋器會解釋成下面這樣的語句:
func = decorator(func)
其實就是把一個函數(shù)當參數(shù)傳到另一個函數(shù)中,然后再回調(diào)
但是值得注意的是裝飾器必須返回一個函數(shù)給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()函數(shù),所以,my_func其實變成了wrapper的一個變量,而后面的my_func()執(zhí)行其實變成了wrapper()
比如:多個decorator
@decorator_one
@decorator_two
def func():
pass
相當于:
func = decorator_one(decorator_two(func))
比如:帶參數(shù)的decorator:
@decorator(arg1, arg2)
def func():
pass
# 相當于:
func = decorator(arg1,arg2)(func)
帶參數(shù)的裝飾器
首先看一下, 如果被裝飾的方法有參數(shù)
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
通常我們都使用更加通用的裝飾器,可以作用在任何函數(shù)或?qū)ο蠓椒ㄉ?,而不必關(guān)心其參數(shù)使用
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}
上邊的示例是帶參數(shù)的被裝飾函數(shù)
現(xiàn)在我們看一下向裝飾器本身傳遞參數(shù)
向裝飾器本身傳遞參數(shù)裝飾器必須使用函數(shù)作為參數(shù),你不能直接傳遞參數(shù)給裝飾器本身
如果想傳遞參數(shù)給裝飾器,可以 聲明一個用于創(chuàng)建裝飾器的函數(shù)
# 我是一個創(chuàng)建裝飾器的函數(shù)
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(裝飾器函數(shù))是decorator_maker(裝飾器生成函數(shù))的內(nèi)部函數(shù)
所以可以使用把參數(shù)加在decorator_maker(裝飾器生成函數(shù))的方法像裝飾器傳遞參數(shù)
# 我是一個創(chuàng)建帶參數(shù)裝飾器的函數(shù)
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
這里裝飾器生成函數(shù)內(nèi)部傳遞參數(shù)是閉包的特性
使用裝飾器需要注意裝飾器是Python2.4的新特性
裝飾器會降低代碼的性能
裝飾器僅在Python代碼導入時被調(diào)用一次,之后你不能動態(tài)地改變參數(shù).當你使用"import x",函數(shù)已經(jīng)被裝飾
使用 functools.wraps最后Python2.5解決了最后一個問題,它提供functools模塊,包含functools.wraps,這個函數(shù)會將被裝飾函數(shù)的名稱、模塊、文檔字符串拷貝給封裝函數(shù)
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__(),這個方法是在我們給某個函數(shù)decorator時被調(diào)用,所以,需要有一個func的參數(shù),也就是被decorator的函數(shù)。
一個是__call__(),這個方法是在我們調(diào)用被decorator函數(shù)時被調(diào)用的
如果decorator有參數(shù)的話,__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 內(nèi)置了三個用于裝飾方法的函數(shù):property、classmethod和 staticmethod。
另一個常見的裝飾器是 functools.wraps,它的作用是協(xié)助構(gòu)建行為良好的裝飾器。
functools.lru_cache 實現(xiàn)了內(nèi)存緩存功能,它可以把耗時長的函數(shù)結(jié)果保存起來,避免傳入相同參數(shù)時重復計算。
我們自己的實現(xiàn)代碼如下:
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)
統(tǒng)計函數(shù)執(zhí)行次數(shù)的裝飾器
def counter(func):
"""
記錄并打印一個函數(shù)的執(zhí)行次數(shù)
"""
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
裝飾器做緩存
帶有過期時間的內(nèi)存緩存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
統(tǒng)計代碼運行時間
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
參考鏈接
How can I make a chain of function decorators in Python?
理解PYTHON中的裝飾器
Python修飾器的函數(shù)式編程
Understanding Python Decorators in 12 Easy Steps!
PEP 0318 -- Decorators for Functions and Methods
PEP 3129 -- Class Decorators
[args and *kwargs? [duplicate]](http://stackoverflow.com/ques...
why-cant-i-set-a-global-variable-in-python
【flask route】
PythonDecoratorLibrary
關(guān)于Python Decroator的各種提案
最后,感謝女朋友支持。
| 歡迎關(guān)注(April_Louisa) | 請我喝芬達 |
|---|---|
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/40745.html
摘要:裝飾器的使用符合了面向?qū)ο缶幊痰拈_放封閉原則。三簡單的裝飾器基于上面的函數(shù)執(zhí)行時間的需求,我們就手寫一個簡單的裝飾器進行實現(xiàn)。函數(shù)體就是要實現(xiàn)裝飾器的內(nèi)容。類裝飾器的實現(xiàn)是調(diào)用了類里面的函數(shù)。類裝飾器的寫法比我們裝飾器函數(shù)的寫法更加簡單。 目錄 前言 一、什么是裝飾器 二、為什么要用裝飾器 ...
摘要:一般情況下,我們使用裝飾器提供的語法糖,來簡化上面的寫法像上面的情況,可以動態(tài)修改函數(shù)或類功能的函數(shù)就是裝飾器。本文標題為會打扮的裝飾器本文鏈接為參考資料修飾器的函數(shù)式編程中的裝飾器介紹思誠之道裝飾器入門與提高賴明星 裝飾器 我們知道,在 Python 中,我們可以像使用變量一樣使用函數(shù): 函數(shù)可以被賦值給其他變量 函數(shù)可以被刪除 可以在函數(shù)里面再定義函數(shù) 函數(shù)可以作為參數(shù)傳遞給另外...
摘要:初步認識裝飾器函數(shù)裝飾器用于在源代碼中標記函數(shù),以某種方式增強函數(shù)的行為。函數(shù)裝飾器在導入模塊時立即執(zhí)行,而被裝飾的函數(shù)只在明確調(diào)用時運行。只有涉及嵌套函數(shù)時才有閉包問題。如果想保留函數(shù)原本的屬性,可以使用標準庫中的裝飾器。 《流暢的Python》筆記本篇將從最簡單的裝飾器開始,逐漸深入到閉包的概念,然后實現(xiàn)參數(shù)化裝飾器,最后介紹標準庫中常用的裝飾器。 1. 初步認識裝飾器 函數(shù)裝飾...
摘要:變量查找規(guī)則在中一個變量的查找順序是局部環(huán)境,閉包,全局,內(nèi)建閉包引用了自由變量的函數(shù)。閉包的作用閉包的最大特點是可以將父函數(shù)的變量與內(nèi)部函數(shù)綁定,并返回綁定變量后的函數(shù),此時即便生成閉包的環(huán)境父函數(shù)已經(jīng)釋放,閉包仍然存在。 導語:本文章記錄了本人在學習Python基礎(chǔ)之函數(shù)篇的重點知識及個人心得,打算入門Python的朋友們可以來一起學習并交流。 本文重點: 1、掌握裝飾器的本質(zhì)、功...
摘要:我們以測量函數(shù)運行時間為例來講一講裝飾器的運行原理。三更加通用的裝飾器前面兩部分講了裝飾器的原理,這一部分就講講要寫出一個通用的裝飾器需要注意的問題。首先就是參數(shù)的問題,裝飾器返回的函數(shù)不是原來的函數(shù),函數(shù)的簽名也就和原來的函數(shù)簽名不一樣。 一、最簡單的裝飾器 裝飾器是python中很基礎(chǔ)也很實用的一個特性。通過裝飾器我們可以很方便地為一些函數(shù)添加相同的功能。我們以測量函數(shù)運行時間為例...
閱讀 1249·2021-11-24 09:39
閱讀 2761·2021-09-28 09:35
閱讀 1139·2019-08-30 15:55
閱讀 1441·2019-08-30 15:44
閱讀 943·2019-08-29 17:00
閱讀 2043·2019-08-29 12:19
閱讀 3372·2019-08-28 18:28
閱讀 758·2019-08-28 18:10