先定义一个简单的函数:

def sum(x, y):
    print('    x + y', eval('x + y'), sep=' = ')
sum(37, 73)
# 运行结果
#     x + y = 110

现在我们假设想要增加上面函数的功能,比如对函数做一个简单的说明,但又不希望修改这个求和函数的定义。此时就需要用到装饰器(Decorator)了。

所谓装饰器,就是在代码运行期间动态增加功能的一种方式,它的本质就是一个返回函数的高阶函数。下面具体给出一个示例:

def description(func):
    def des(*args, **kw_args):
        print('{} is a sum function:'.format(func.__name__))
        return func(*args, **kw_args)
    return des


@description
def sum(x, y):
    print('    x + y', eval('x + y'), sep=' = ')


sum(73, 37)
# 运行结果
# sum is a sum function:
#     x + y = 110

description函数是一个decorator,所以它接受一个函数作为参数,并返回一个函数。

把@description放到sum()函数的定义处,相当于执行了语句:sum = description(sum)。

对于上述代码的解释:首先,由于description()是一个装饰器,返回一个函数,所以原始的sum()函数仍然存在,只是同名的sum变量指向了一个新的函数,于是调用sum(37, 73)将执行新函数即在description()函数中返回的des()函数。在des()函数内,首先对原始函数的说明信息,然后调用原始函数。

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数。例如,我们现在想自定义对函数的说明信息,而不是由系统指定:

def description(information):
    def des_decorator(func):
        def des(*args, **kw_args):
            print('%s: %s' % (func.__name__, information))
            return func(*args, **kw_args)
        return des
    return des_decorator


@description('a sum function:')
def sum(x, y):
    print('    x + y', eval('x + y'), sep=' = ')


print(sum.__name__)
sum(73, 37)
# 运行结果
# des
# sum: a sum function
#     x + y = 110

上述代码调用sum(73, 37)时的情况是这样的:sum = description('a sum function')(sum)。先执行description('a sum function'),返回的是一个decorator,再调用返回的函数并传入参数sum()函数,返回值为des()函数。在des()函数内,首先对原始函数的说明信息,然后调用原始函数。

当前存在的问题:我们已在代码中说明,经过装饰器修饰后的sum()函数,sum.__name变为了des,因此我们需要对此进行修改。将原始函数达的__name__等属性复制到des()函数中,否则,有些依赖于函数签名的代码段就会出现意想不到的错误:

import functools


def description(func):
    @functools.wraps(func)
    def des(*args, **kw_args):
        print('{} is a sum function:'.format(func.__name__))
        return func(*args, **kw_args)
    return des

对部分代码做如上修改即可。

示例:请编写一个decorator,能在函数调用的前后打印出'begin call'和'end call'的日志。
再思考一下能否写出decorator,使它既支持:

@multi_decorator
def f():
    pass

又支持:

@multi_decorator('information')
def f():    pass
import functools


def multi_decorator(*t_args):
    def m_decorator(func):
        @functools.wraps(func)
        def dec(*args, **kw_args):
            if len(t_args) > 0:
                print('%s' % t_args[0])
            else:
                print('begin call')
            func(*args, **kw_args)
            print('end call')
        return dec
    return m_decorator


@multi_decorator()
def sum(x, y):
    print('x + y = %d' % (x + y))


@multi_decorator('execute the function')
def sum2(x, y):
    print('x + y = %d' % (x + y))


sum(73, 37)
print("------- ------- -------")
sum2(73, 37)
# 运行结果
# begin call
# x + y = 110
# end call
# ------- ------- -------
# execute the function
# x + y = 110
# end call