python 装饰器

装饰器本质是一个函数,这个函数的返回值是一个函数,我们需要关心两个问题:

  1. 什么时候被调用
  2. 调用时如何传递

理解这两个问题,就搞清楚了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)

results matching ""

    No results matching ""