添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

hello是我们最开始的函数,通过decorator对hello执行前后进行了加工,输出也都符合预期。但是有个问题是:我们修改了每个调用hello的地方,改为decorator(hello),是否有办法可以不修改调用处的语句?

重新定义原函数

def hello():
    print 'hello world!'
def decorator(func):
    def _decorator():
        print 'before %s' % func.__name__
        func()
        print 'after %s' % func.__name__
    return _decorator
hello = decorator(hello)
hello()

只要重新定义hello这个变量(函数名/函数指针),那么我们不需要修改调用hello函数的地方了。我们在decorator函数下,又实现了一层_decorator,hello重新指向了这个函数,就达到我们的目的了。_decorator也可以返回func()的函数值,这样跟func就更接近了。

python里的实现

上述代码对应python里的实现

def decorator(func):
    def _decorator():
        print 'before %s' % func.__name__
        func()
        print 'after %s' % func.__name__
    return _decorator
@decorator
def hello():
    print 'hello world!'
hello()

@只是一个语法,@decorator def func(): ...def func(): ... func = decorator(func)并没有什么区别,只不过更直观的指出了这是一种装饰器。

函数带参数

def decorator(func):
    def _decorator(a, b):
        print 'before %s' % func.__name__
        result = func(a, b)
        print 'after %s' % func.__name__
        return result
    return _decorator
@decorator
def add(a, b):
    print 'add called.'
    return a + b
print add(1, 2)

函数带参数、返回值的情况只要在第二层的函数里对应的实现就可以了。

函数带任意参数

def decorator(func):
    def _decorator(*args, **kwargs):
        print 'before %s' % func.__name__
        result = func(*args, **kwargs)
        print 'after %s' % func.__name__
        return result
    return _decorator
@decorator
def add1(a, b):
    print 'add1 called.'
    return a + b
@decorator
def add2(a, b, c):
    print 'add2 called.'
    return a + b + c
print add1(1, 2)
print add2(1, 2, 3)

装饰器带参数

带参数的装饰器又额外在外面实现了一层,可以认为decorator_args(arg)与之前的decorator(func)是等价的,实现上也可看到,decortor_args(arg)实际上返回了一个两层的decorator. 可以看做myfunc = _decorator(func) = _decorator_args(arg)(func)

def decorator_args(args):
    def decorator(func):
        def _decorator(a, b):
            print 'before %s args[%s]' % (func.__name__, args)
            result = func(a, b)
            print 'after %s args[%s]' % (func.__name__, args)
            return result
        return _decorator
    return decorator
@decorator_args('arg1')
def add(a, b):
    print 'add called.'
    return a + b
@decorator_args('arg2')
def add100(a, b):
    print 'add called.'
    return a + b
print add(1, 2)
print add100(1, 2)

回到这个代码里来,hello经过装饰后有什么潜在的问题吗?

def decorator(func):
    def _decorator():
        print 'before %s' % func.__name__
        func()
        print 'after %s' % func.__name__
    return _decorator
@decorator
def hello():
    print 'hello world!'
print hello.__name__

输出是_decorator

可以看到__name__并不是hello了,我们可以在_decorator的实现里加上_decorator.__name__ = func.__name__,但是对于__doc__这一类的呢?

如何保留原有函数的属性

这个时候就需要我们的functools登场了

import functools
def decorator(func):
    @functools.wraps(func)
    def _decorator():
        print 'before %s' % func.__name__
        func()
        print 'after %s' % func.__name__
    return _decorator
@decorator
def hello():
    print 'hello world!'
print hello.__name__

装饰过的函数的属性被保留了下来。

这个例子是从伯乐在线上的一片文章摘抄过来的,主要功能是对函数的执行时间计时。

import time
import functools
def logged(time_format):
    def decorator(func):
        @functools.wraps(func)
        def decorated_func(*args, **kwargs):
            print '%s called on %s' %\
                (func.__name__, time.strftime(time_format))
            start_time = time.time()
            result = func(*args, **kwargs)
            end_time = time.time()
            print '%s finised, executed time = %0.3fs' %\
                (func.__name__, end_time - start_time)
            return result
        return decorated_func
    return decorator
@logged('%b %d %Y - %H:%M:%S')
def add1(x, y):
    time.sleep(1)
    return x + y
@logged('%H:%M:%S')
def add2(x, y):
    time.sleep(2)
    return x + y
add1(1, 2)
add2(1, 2)
add1 called on Aug 04 2015 - 20:04:35
add1 finised, executed time = 1.003s
add2 called on 20:04:36
add2 finised, executed time = 2.005s

This work is licensed under a Attribution-NonCommercial 4.0 International license.[转载需注明出处。]