Python函数式编程03 高阶函数与匿名函数

0

函数式编程的概念

编程有多种范式,每种范式都代表了使用程序处理问题的思维方式。

命令式编程是一种常见的编程范式,在命令式编程中,每一条语句通过一定方式改变变量的值,推进处理过程逐渐到预期结果。

函数式编程(functional programming)也是一种编程范式,它与命令式编程最重要的特征区别是状态。函数式编程并不需要修改变量,而是通过每一次调用函数创造出一个或多个新的结果,然后再用得到的结果参与新的运算,最终得到合适的结果。纯粹的函数式编程避免了由于使用变量赋值导致程序显式维护计算状态而带来的复杂性。

函数式编程会基于一些基本的、功能清晰的简单函数,将其组合形成功能更抽象、符合业务需要的高级函数。相比由基础指令组成的复杂流程,函数式编程更容易理解其执行的过程。

大部分编程语言都提供了函数,不过不是每一种编程语言都支持使用函数式编程。Python 不是纯粹的函数式语言,不过 Python 语言的特性使得它可以使用函数式编程的方式去编写程序。

接下来将会根据函数式编程的特点,介绍 Python 中函数的高级特性。后续几节的内容,都围绕以下概念展开:

  1. 函数式编程中,函数可以用作其他函数的参数或者返回值。要做到这一点,函数必须是运行时环境中的头等对象
  2. 一个函数在运行时不能影响到别的函数的结果,所以需要构建复杂的不可变数据结构
  3. 函数式编程会将计算推迟到需要的时候进行,即非严格求值(惰性求值)
  4. 一个函数可能在执行时调用自身,这称为递归。函数式编程可以通过递归代替循环语句,这样可以不用产生跟踪循环状态的开销

头等函数

高阶函数

在 Python 中,函数通常是通过 def 语句创建的对象,它与其它对象例如数值、列表甚至模块都没有本质上的区别。在创建一个函数后,可以检查其类型:

>>> def func(a, b, c=3, *d):
... pass
...
>>> type(func)
<class 'function'>

结果表明,func 的类型是“函数”。

可以通过检查其属性来分析函数对象的一些特点:

>>> func.__name__
'func'
>>> func.__defaults__
(3,)
>>> func.__code__.co_varnames
('a', 'b', 'c', 'd')

甚至可以为函数对象添加上自定义的属性:

>>> func.level = 1
>>> func.self = func

一些大型框架通过函数的自定义属性来获取函数的元属性,除此之外一般情况下很少用到。不过通过以上的介绍,可以明白 Python 中函数与其它对象并没有本质区别。完全可以将函数对象赋值给一个变量,再通过调用变量获取结果:

>>> my_sum = sum
>>> my_sum([1, 4, 5, 8, 9])
27

因此,一个函数完全可以用作参数传入另一个函数中,还可以作为返回值从另一个参数返回。反之,如果一个函数接受一个或多个函数作为输入,或者输出一个函数作为返回值,则称该函数为高阶函数

以下就是一个简单的高阶函数:

def call(f, *args, **kwargs):
    return f(*args, **kwargs)

可以将一个函数对象作为参数传递给它,例如:

>>> call(pow, 2, 3, 4)
0

高阶函数在函数式编程中用途广泛。接下来先介绍匿名函数,后续再研究高阶函数的使用方法。

匿名函数

所谓匿名函数,是指不用 def 语句来创建的函数。匿名函数短小简洁,不需要专门定义,适合许多一次性使用的场景。

Python 中使用关键字 lambda 来创建一个匿名函数,它的语法为:

lambda param1, param2, ... : expression

其中函数的返回值即为冒号后表达式 expression 的值。以下是一个示例:

lambda x, y : x + y ** 2

一个 lambda 表达式,相当与隐式定义了如下的函数:

def lambda(param1, param2, ...):
    return expression

尽管这个等价形式从语法上来说是错误的。不过,lambda 也可以使用普通函数具有的各种特性,例如可变参数、默认参数等。

一个 lambda 表达式即是一个函数对象,可以把一个 lambda 表达式赋值给一个变量,通过变量调用匿名函数,例如:

>>> power = lambda a, n: a ** n
>>> power(3, 4)
81

利用 lambda 函数也可以非常方便地调用一个高阶函数,只需要在调用函数时传入一个 lambda 函数作为参数即可,例如:

>>> call(lambda x, y, z: x / y - z, 10, 2, 4)
1.0

或者也可以直接利用 lambda 函数作为一个函数的返回值,例如:

>>> def add_n(n):
... return lambda x : n + x
...
>>> add_30 = add_n(30)
>>> add_30(-10)
20

注意第一次调用高阶函数,返回了另一个函数,需要再次调用返回的结果。

既然一个 lambda 表达式就是一个函数对象,完全可以在定义完之后马上就调用:

>>> (lambda seq, n, p: seq[n] ** p)([1, 2, 3], 1, 5)
32

注意为了防止歧义,需要将整个 lambda 表达式使用括号表示为一个整体。

这种定义完马上执行就抛弃的自执行函数看似多此一举,实际上它也是有用途的。例如在异步编程时,为了防止函数引用的一个全局变量被意外修改,就可以使用自执行函数将其变成一个参数,函数马上执行使得它变成了一个被“冻结”的局部变量。

纯函数

为了达到纯粹,函数式编程要求避免函数改变可变对象的状态。变量不会在全局范围内发生变化,这就要求在函数内不能使用 global 语句。除此之外,引用外部的可变对象也应该小心,因为可能在不经意间修改了可变对象。

没有副作用的函数符合在数学中函数的纯粹定义,也称为纯函数

京ICP备2021034974号
contact me by hello@frozencandles.fun