高阶函数

在python里面decorator看起来是个挺高端大气上档次的词, 不过这个东西在函数式语言里面其实很稀松平常, 用一句话概括起来就是高阶函数的语法糖. 什么叫高阶函数(high order procedures)呢? 就是返回值或者参数是函数的函数. 比如我们现在有个函数

1
hello_world
:

def hello_word():
    print "hello world!"

这个函数的个功能是打印一行字”hello world”. 现在我们想写一个函数, 它的功能是把

1
hello_word
这行字打印10遍, 怎么做呢? 简单:

def hello_world_10():
    for i in range(10):
        hello_world()

现在我们站的更高一点, 希望写一个更通用的函数, 可以将任意函数执行任意10次, 要怎么办呢?

def ten_times(func):
    def new_fun():
        for i in range(10):
            func()
    return new_fun

hello_world_10 = ten_times(hello_world)
hello_world_10()

可以看到,

1
ten_times
这个函数比我们一般的函数更”高级”一些, 它接受的参数是一个函数, 把这个函数进行一些加工(执行10次)后, 返回一个新的函数. 这个函数就是高阶函数.

decorator

理解了高阶函数后, 我们就可以看看decorator了. 我们把前一个例子改为:

@ten_times
def hello_world_10():
    print "hello world!"

这样, 新的

1
hello_world_10
和原来的版本完全等价. 所以说,
1
@ten_times
其实是以下代码的语法糖:

def hello_world_10():
    print "hello world!"
hello_world_10 = ten_times(hello_world_10)

原函数带参数

假设我们有如下

1
add
函数:

def add(a, b):
    return a+b

这一次, 我们的

1
ten_times
函数希望把原函数的_返回值_乘以10, 怎么做呢?

def ten_times(func):
    def new_func(*args, **kwargs):
        return 10 * func(*args, **kwargs)
    return new_func

@ten_times
def add_10(a, b):
    return a+b

由此可见, 我们在decorator的定义中, 将新函数的输入传递给原函数即可.

decorator带参数

这次我们想把

1
ten_times
写的更加general一些, 我们希望它可以接受一个参数
1
times
, 可以将任意函数执行的结果乘以任意次. 首先我们用高阶函数的方式:

def times(time):
    def times_f(func):
        def new_func(*args, **kwargs):
            return time * func(*args, **kwargs)
        return new_func
    return times_f

def add(a, b):
    return a+b

ten_times = times(10)
add_10 = ten_times(add)

现在有点复杂了, 对吧?

1
times
首先接受一个参数
1
time
, 返回一个函数
1
times_f
, 这个
1
times_f
函数接受一个函数
1
func
作为参数, 然后又返回一个新的函数
1
new_func
. 换句话说, 这个函数返回了一个函数, 后者又返回一个函数. 注意到
1
new_func
引用了
1
times_f
中的局部变量
1
func
, 这样的函数被称为闭包(closure). 用decorator的语法糖要怎么写呢?

@times(10)
def add_10(a, b):
    return a+b

从之前的经验我们可以看到,

1
ten_times
这个函数需要返回一个函数, 所以这里我们知道
1
times(10)
也是一个函数,它的返回值是一个函数, 由此可知
1
times
是一个返回(返回函数的函数)的函数, 也印证了前面的分析.

@times(10)
def add_10(a, b):
    return a+b

一点小问题

我们还是回到比较简单的

1
ten_times
这个函数:

@ten_times
def add(a, b):
    return a+b
 
>>> add.__name__
'new_func'

我们看到,

1
add
1
__name__
属性变成了
1
new_func
! 仔细想想, 这很正常, 因为ten_times返回的函数就是
1
new_func
呀! 我们先暴力fix一下:

def ten_times(func):
    def new_func(*args, **kwargs):
        return 10 * func(*args, **kwargs)
    new_func.__name__ = func.__name__
    return new_func

@ten_times
def add_10(a,b):
    return a+b

>>> add_10
<function add_10 at 0x10da62140>

的确, 这个问题解决了. 不过, 还有

1
__doc__
等等属性呢? 每次声明decorator的时候都要这么搞一下, 不蛋疼么? 是的, 我们可以把这些fix的代码放到一起:

def fix_props(func):
    def wrapper(f):
        def new_f(*args, **kwargs):
            return f(*args, **kwargs)
        new_f.__name__ = func.__name__
        new_f.__doc__ = func.__doc__
        return new_f
    return wrapper


def ten_times(func):
    @fix_props(func)
    def new_func(*args, **kwargs):
        return 10 * func(*args, **kwargs)
    return new_func

这样, 就帮我们完成了这些琐碎的小问题.

1
fix_props
这个decorator可以将原函数的属性复制到新的wrapper函数, 使得原函数的docstring, name等不会丢失. 万一python升级了, 函数又有新的属性了, 肿么办?幸运的是, python里面有专门的库函数可以解决这个问题:

from functools import wraps

def ten_times(func):
    @wraps(func)
    def new_func(*args, **kwargs):
        return 10 * func(*args, **kwargs)
    return new_func

这个

1
wraps
的原理就和我们上面的
1
fix_props
类似.

应用

decorator在python里有挺多应用的, 最典型的是

1
@classmethod
1
@property
. 例如:

class A(object):
    def __init__(self):
        self.__x = 0

    @property
    def x(self):
        return self.__x

    @x.setter
    def x(self, xval):
        self.__x = xval

    @classmethod
    def my_name(cls):
        return "A"

>>> A.my_name()
'A'
>>> a = A()
>>> a.x = 1
>>> a.x
1