python 装饰器
装饰器本质是一个函数,这个函数的返回值是一个函数,我们需要关心两个问题:
- 什么时候被调用
- 调用时如何传递
理解这两个问题,就搞清楚了python装饰器
定义一个装饰器
# 函数的参数是func
def decorator(func):
def wrapper():
print "before call func"
func()
print "after call func"
# 返回一个函数
return wrapper()
装饰器的调用
使用@
可以添加一个装饰器
@decorator
def test():
print "Leave me alone"
# 执行test函数时,先会自动调用decorator函数返回wrapper
# 然后再执行wrapper函数
test()
常用例子
def fault_tolerance(func):
def wrapper():
try:
return func()
except Exception as e:
pass
return wrapper
wrapper函数的参数
从上面的例子可以看出,当调用test函数时,实际上执行的是wrapper函数
有个问题是,定义wrapper之前,实际上不知道test执行的参数,所以wrapper函数必须动态接受传入的参数
可以使用 *args, **kwargs
猛击
def fault_tolerance(func):
def wrapper():
try:
return func(*args, **kwargs)
except Exception as e:
pass
return wrapper
向装饰器传递参数
装饰器是返回一个wrapper函数,二级装饰器就是返回一个decorator,根据不同的参数,返回不同的decorator
# 统计函数的请求数量
def record_stage_empty_response(metric):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
response = func(*args, **kw)
if not response:
statsd.incr('key1.key2.{}.empty'.format(metric))
return response
return wrapper
return decorator
# 必须要执行record_stage_empty_response返回一个decorator
@record_stage_empty_response('keys')
def func():
pass
多个装饰器
一个函数可以使用多个装饰器, 千万要注意装饰器的顺序,否则会出现bug, 最外层的装饰器最先被执行。
```python
@bread
@ingredients
def sandwich(food="--ham--"):
print food
# 等价于:
bread(ingredients(sandwich()))
使用装饰器
def benchmark(func):
"""
装饰器打印一个函数的执行时间
"""
import time
def wrapper(*args, **kwargs):
t = time.clock()
res = func(*args, **kwargs)
print func.__name__, time.clock()-t
return res
return wrapper
def logging(func):
"""
装饰器记录函数日志
"""
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
print func.__name__, args, kwargs
return res
return wrapper
def counter(func):
"""
记录并打印一个函数的执行次数
"""
def wrapper(*args, **kwargs):
wrapper.count = wrapper.count + 1
# 执行了func,并返回执行后的值
res = func(*args, **kwargs)
print "{0} has been used: {1}x".format(func.__name__, wrapper.count)
return res
wrapper.count = 0
return wrapper
# 把一个函数变成守护进程
def daemon_decorator_maker(name, wait):
def mid_func(func):
@functools.wraps(func)
def wrapper(*args):
while True:
try:
func(*args)
time.sleep(wait)
except Exception:
time.sleep(wait)
return wrapper
return mid_func
# 缓存class的property
def cached_property(func):
"""Memoize property for class
"""
@functools.wraps(func)
def wrapper(self):
if not hasattr(self, "_cached_property"):
self._cached_property = {}
if func.__name__ not in self._cached_property:
self._cached_property[func.__name__] = func(self)
return self._cached_property[func.__name__]
return property(wrapper)
# 生成key
def gen_keygenerator(namespace, func):
args = inspect.getargspec(func)
prefix = "{}:{}|{}".format(func.__module__, func.__name__, namespace)
has_self = args[0] and args[0][0] in ("self", "cls")
def generate_key(*args, **kw):
if has_self:
args = args[1:]
tuples = sorted(kw.iteritems())
return "{}|{}{}".format(prefix, args, tuples)
return generate_key
functools
函数是一个对象,函数会有自己的属性,使用dir(func_name)
可以查看这个函数对象
# 函数是一个对象
def func_obj(a, b):
'''
this id doc
'''
pass
print func_obj.__doc__
print func_obj.__module__
print func_obj.__name__
# 对象的属性是可以修改的
func_obj.__doc__ = "updated doc"
print func_obj.__doc__
# output:
# this id doc
# __main__
# func_obj
# updated doc
被装饰器装饰之后,因为装饰器返回的是wrapper
, 而不是原来的func
, 所以,返回的后的函数的属性变成了wrapper
def my_decorator(f):
def wrapper(*args, **kwds):
print 'Calling decorated function'
return f(*args, **kwds)
return wrapper
@my_decorator
def example():
"""这里是文档注释"""
print 'Called example function'
example()
# outputs:
# Calling decorated function
# Called example function
# wrapper 函数名变成了wrapper,而非example
# None
如何避免这种情况,我们需要使用到functools模块
from functools import wraps
def my_decorator(f):
@wraps(f)
def wrapper(*args, **kwds):
print 'Calling decorated function'
return f(*args, **kwds)
return wrapper
@my_decorator
def example():
"""这里是文档注释"""
print 'Called example function'
example()
# 下面是输出
"""
Calling decorated function
Called example function
"""
print example.__name__ # 'example'
print example.__doc__ # '这里是文档注释'
functools 模块
functools 模块中有三个主要的函数 partial(), update_wrapper() 和 wraps(), 下面我们分别来看一下吧。
partial(func[,args][, *keywords])
def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords):
newkeywords = keywords.copy()
newkeywords.update(fkeywords)
return func(*(args + fargs), **newkeywords)
# 给函数对象添加新的属性
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
OK,可能一下子没看明白,那么继续往下看,看一下是怎么用的。我们知道 python 中有个 int([x[,base]]) 函数,作用是把字符串转换为一个普通的整型。如果要把所有输入的二进制数转为整型,那么就要这样写 int('11', base=2)。这样写起来貌似不太方便,那么我们就能用 partial 来实现值传递一个参数就能转换二进制数转为整型的方法。
from functools import partial
int2 = partial(int, base=2)
print int2('11') # 3
print int2('101') # 5
update_wrapper(wrapper, wrapped[, assigned][, updated])
看这个函数的源代码发现,它就是把被封装的函数的 module, name, doc 和 dict 复制到封装的函数中去,源码如下,很简单的几句:
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
for attr in assigned:
setattr(wrapper, attr, getattr(wrapped, attr))
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
return wrapper
具体如何用我们可以往下看一下。 wraps(wrapped[, assigned][, updated]) wraps() 函数把用 partial() 把 update_wrapper() 给封装了一下。
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)