Contents
1. 高阶函数和柯理化
1.1. 高阶函数(High-Order Function)
一个函数要成为高阶函数,需要满足下面至少一个条件:
- 函数定义中接收一个或者多个函数作为参数
- 返回值为函数
满足上述两点条件的任意一点,即可将该函数称为高阶函数。
Python中内建了一些高阶函数,比如sorted
,比如min
这类可以接收函数作为参数的内建函数;另外,比如偏函数functools.partial
,这个函数接收函数作为参数,同时将函数作为返回值。函数装饰器通常都属于高阶函数,装饰器既需要函数作为参数,也需要将函数作为返回值。
下面实现一个自定义的高阶函数。具体代码如下所示:
def echo_hw(arg):
print('Hello World! This is {}'.format(arg))
def high_order_func(func, arg):
func(arg)
print('Welcome to Python!')
high_order_func(echo_hw, 'Python')
上述代码的输出结果如下所示:
Hello World! This is Python
Welcome to Python!
上述代码并没有什么实际意义,只是一种定义高阶函数的方式——接收函数作为参数的函数,可以被称为是高阶函数。
将上述代码稍作改变,使其返回值为参数——也可以被称为是高阶函数。具体代码如下所示:
def high_order_func(arg):
def echo_hw():
print('Hello World! This is {}'.format(new_arg))
new_arg = arg + '\nWelcome to Python!'
return echo_hw
ret_func = high_order_func('Python')
ret_func()
上述函数的输出结果如下所示:
Hello World! This is Python
Welcome to Python!
上述函数代码同样没有什么实际意义,只是说明如果一个函数的返回值是函数,那么这个函数也可以被称为是高阶函数。
上面的两种代码实现方式虽然有差异,但是实现的目的是相同的,且都符合高阶函数的构成条件,所以都是高阶函数。
1.2. 柯里化(Currying)
而所谓的柯里化(Currying),则是指,将接收多个参数的函数转换为单参函数,并将剩余的参数封装在该单参函数内的嵌套函数中,作为嵌套函数的参数(嵌套函数可以接收所有剩余的参数,也可以接收剩余参数中的一个或者几个,并将此后剩余的参数再继续构建嵌套函数)。通过嵌套函数以及闭包作用域,将多参函数的参数拆解到多个嵌套函数中。柯里化的本质是嵌套函数以及闭包作用域下的变量查询。柯里化是实现函数装饰器的一个手段。
下面通过示例,说明如何将一个普通函数进行柯里化改稿的过程。
一个多参普通函数,如下所示:
def multi_add(a, b, c): return a + b + c res = multi_add(1, 2, 3) print(res)
上述函数的输出结果为6。
将上述普通函数通过柯里化改造为func(a)(b, c)
的形式。
改造代码具体如下所示:
def multi_add_cury(a): def wrapper(b, c): return a + b + c return wrapper ret_func = multi_add_cury(5) res = ret_func(6, 7) print(res)
上述即为柯里化之后的函数,需要进行两次函数调用(伴随着两次参数传递过程)才能获得最终的执行结果,其执行结果为18。
将上述普通函数通过柯里化改造为func(a)(b)(c)
的形式。
改造代码具体如下所示:
def multi_add_cury1(a): def wrapper(b): def inner(c): return a + b + c return inner return wrapper wrap_func = multi_add_cury1(7) inner_fun = wrap_func(8) res = inner_fun(9) print(res)
上述即为柯里化之后的函数,需要进行三次函数调用(伴随着三次参数传递过程)才能获得最终的执行结果。其计算结果为24。
最后还可以将普通函数通过柯里化改造为func(a, b)(c)
的形式。
改造代码具体如下所示:
def multi_add_cury2(a, b): def wrapper(c): return a + b + c return wrapper wrap_func = multi_add_cury2(11, 12) res = wrap_func(13) print(res)
上述即为柯里化之后的函数,需要经过两次函数调用(伴随两次参数传递过程)才能获得最终的计算结果。上述执行结果为36。
上述即为函数柯里化的过程以及方式。
2. functools包内的每个函数的功能作用
在functools
模块中提供了一些高阶函数,用于实现用户自己的通用函数。在我安装的Python-3.7中,包含的方法个数为9个,分别如下所示:
-
functools.cmp_to_key
:这个高阶函数的作用是将老式的比较函数转换为新式的key形式函数,以便其可以用在接收key=key_func
作为参数的函数(比如sorted()
,min()
,max()
,heapq.nlargest()
,heapq.nsmallest()
,itertools.groupby()
等等这类函数)中。这类函数中的key
参数通常要求指定的函数接收1个参数,所以就需要这个cmp_to_key
高阶函数将传统的接受2个参数的比较函数转换为接受1个参数的key形式函数。所谓的比较函数,是指接收2个参数,并比较这两个参数的大小,如果是小于的关系,则返回负数;如果相等,则返回0;如果是大于关系,则返回正数。而key形式函数是一个可调用对象,其接受1个参数,并且返回另一个对象,使用这个返回的对象作为key=
的参数值。这个函数的帮助基本信息如下所示:
functools.cmp_to_key(func)
下面通过
sorted
函数中的key=
参数,示例这个functools.cmp_to_key
高阶函数的使用方式。具体如下所示:from functools import lru_cache, cmp_to_key res = sorted([7, 2, 3, 1, 5, 8], key=cmp_to_key(lambda x, y: x - y)) print(res)
上述函数的执行结果如下所示:
[1, 2, 3, 5, 7, 8] Process finished with exit code 0
通过上述函数就完成了参数的转换。但是这个背后过程并不很清楚。
下面再看两个关于
sorted
函数中不同的key=
参数取值情况的排序表现。当
sorted
函数的key=
参数不适用functools.cmp_to_key
高阶函数的时候,其排序表现如下所示:sorted([7, 3, 2, 1, 5, 6, 9, 8], key=lambda x: x)
上述语句的执行结果如下所示:
[1, 2, 3, 5, 6, 7, 8, 9]
从上述结果中可以看出,对结果进行了排序,其实省略掉
key=
这个参数,也是默认按照升序排列的。接下来对key=
的参数稍作调整,具体如下所示:sorted([7, 3, 2, 1, 5, 6, 9, 8], key=lambda x, y: x, y)
此时执行报错,具体如下所示:
File "C:\Users\ikkys\AppData\Local\Temp/ipykernel_187720/3200397783.py", line 1 sorted([7, 3, 2, 1, 5, 6, 9, 8], key=lambda x, y: x, y) ^ SyntaxError: positional argument follows keyword argument
上面的异常信息中提示,在关键字参数后面跟了一个位置参数。因为
key
参数要求指定的函数为单参函数,所以这里指定两个是不合适的。接下来使用
functools.cmp_to_key
这个高阶函数将接收两个参数的比较函数转换为单参函数。具体如下所示:
sorted([7, 3, 2, 1, 5, 6, 9, 8], key=cmp_to_key(lambda x: x))
上述的执行结果会报错,具体如下所示:
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) ~\AppData\Local\Temp/ipykernel_187720/3510372626.py in <module> ----> 1 sorted([7, 3, 2, 1, 5, 6, 9, 8]