函数是一个引用类型,函数对象可以被赋予给变量,然后通过变量调用:

函数对象有一个__name__属性,可以获得函数的名字:

 

如果我们现在要增强now()函数的功能(比如在执行now函数前打印日志),但是又不想修改now()函数,在代码运行期间动态增加功能的方式,叫做装饰器。

 

本质上,装饰器(decorator)就是一个"返回值是一个函数"的高阶函数,满足打印日志的装饰器示例如下:

def log(func):
    def wrapper(*args, **kw):
        print 'call %s():' % func.__name__
        return func(*args, **kw)
    return wrapper

 

上面的装饰器接收一个函数作为参数,并返回一个函数,我们使用@语法,把decorator置于函数定义处:

@log

def now():

    print '20160831'

调用函数now,执行如下:

 

把@log放到now()函数的定义处,相当于执行了语句:

now = log(now)

也就是now变量重新进行了赋值,返回值是wrapper函数,先打印日志,然后再调用原始函数。

 

如果装饰器本身需要传入参数,那就需要编写一个返回装饰器的高阶函数,如下:

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print '%s %s():' % (text, func.__name__)
            return func(*args, **kw)
        return wrapper
    return decorator

这个三层嵌套的装饰器用法如下:

@log('execute')
def now():
    print '2013-12-25'

执行结果如下:

>>> now()
execute now():
2013-12-25  

三层嵌套相当于执行

>>> now = log('execute')(now)

首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper'

>>> now.__name__
'wrapper'   

因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。
使用Python内置的functools.wraps,完整的装饰器如下:

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print 'call %s():' % func.__name__
        return func(*args, **kw)
    return wrapper

或者带参数的装饰器:

import functools

def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print '%s %s():' % (text, func.__name__)
            return func(*args, **kw)
        return wrapper
    return decorator