一文带您了解Python的函数式编程:理解lambda、map()、filter()和reduce()

开发 前端
函数式编程是一种编程范式,其中主要的计算方法是纯函数的求值。尽管 Python 主要不是函数式语言,但您仍然可以按照函数式编程原则编写 Python。

函数式编程在数据处理领域中扮演着重要的角色,其优势在于能以简洁和直观的方式处理和转换数据。通过将数据转换操作封装在纯函数中,函数式编程避免了副作用和可变状态,提升了代码的可维护性和可读性。在处理数据时,函数式编程提供了强大的工具,如 lambda、map()、filter() 和 reduce(),这些工具允许开发者高效地应用操作、筛选和归约数据集合。利用这些函数,数据处理可以变得更加简洁、模块化。这种编程范式不仅有助于编写更清晰的代码,还能帮助开发者应对复杂的数据处理任务,实现更高效的数据流转和分析。

什么是函数式编程?

纯函数是指输出值完全由输入值决定,并且没有任何可观察的副作用的函数。在函数式编程中,程序主要由纯函数的计算组成。计算通过嵌套或组合的函数调用进行,而不会改变状态或可变数据。

函数式编程范式之所以受欢迎,是因为它相对于其他编程范式有几个优势。函数式代码具有以下特点:

  • 高级抽象:您描述的是想要的结果,而不是明确指定如何一步步达成这个结果。单个语句通常简洁但功能强大。
  • 透明性:纯函数的行为可以通过其输入和输出来描述,而无需依赖中间值。这消除了副作用的可能性,并有助于调试。
  • 并行化:不引发副作用的例程可以更容易地彼此并行运行。

许多编程语言都在一定程度上支持函数式编程。在某些语言中,几乎所有代码都遵循函数式编程范式。Haskell 就是这样的例子。而 Python 则同时支持函数式编程和其他编程模型。

虽然函数式编程的详细描述确实较为复杂,但这里的目标并不是提供严格的定义,而是展示如何在 Python 中进行函数式编程。

Python 对函数式编程的支持如何?

为了支持函数式编程,如果一种编程语言中的函数能够做到以下两点,将会非常有利:

  1. 接受另一个函数作为参数
  2. 返回另一个函数给调用者

Python 在这两个方面都表现得很好。在 Python 中,一切皆为对象,所有对象在 Python 中的地位基本上是平等的,函数也不例外。

在 Python 中,函数是第一类公民。这意味着函数具有与字符串和数字等值相同的特性。任何可以对字符串或数字进行的操作,也可以对函数进行。

例如,您可以将一个函数赋值给一个变量,然后可以像使用该函数一样使用该变量:

def func():
    print("I am function func()!")




func()


another_name = func
another_name()

图片图片

在第 7 行,通过 another_name = func 这条语句创建了一个新的引用,指向函数 func(),这个引用名为 another_name。随后,您可以通过 func 或 another_name 这两个名称来调用这个函数,如第 5 行和第 8 行所示。

您可以使用 print() 将函数显示在控制台上,还可以将函数作为元素包含在复合数据对象(例如列表)中,甚至可以将其用作字典的键:

def func():
    print("I am function func()!")


print("cat", func, 42)


objects = ["cat", func, 42]
print(objects[1])


objects[1]()


d = {"cat": 1, func: 2, 42: 3}
d[func]

图片图片

在这个示例中,func() 出现在与值 "cat" 和 42 相同的上下文中,python解释器都能正常处理它。

在当前讨论的上下文中,关键在于 Python 中的函数满足了前面提到的对函数式编程有利的两个标准。您可以将一个函数作为参数传递给另一个函数:

def inner():
    print("I am function inner()!")


def outer(function):
    function()


outer(inner)

图片图片

以上示例的过程如下:

  • 在第 7 行中,inner() 被作为参数传递给 outer()。
  • 在 outer() 内部,Python 将 inner() 绑定到函数参数 function。
  • 然后 outer() 可以直接使用 function 来调用 inner()。

这被称为函数组合。需要注意的是,您传递的是函数对象本身作为参数。如果您使用括号来调用函数对象,那么您传递的将不是函数对象,而是它的返回值。

当您将一个函数传递给另一个函数时,被传递的函数有时被称为回调函数(callback),因为对内部函数的调用可以修改外部函数的行为。

一个很好的例子是 Python 中的 sorted() 函数。通常,如果您将一个字符串列表传递给 sorted(),它会按照字典顺序进行排序:

图片图片

然而,sorted() 接受一个可选的 key 参数,该参数指定一个回调函数作为排序的依据。因此,例如,您可以按照字符串的长度进行排序:

animals = ["ferret", "vole", "dog", "gecko"]
sorted(animals, key=len)

图片图片

sorted() 还可以接受一个可选的参数,用于指定是否以反向顺序排序。但是,您也可以通过定义自己的回调函数来实现相同的效果,例如编写一个函数来反转 len() 的排序顺序:

animals = ["ferret", "vole", "dog", "gecko"]
sorted(animals, key=len, reverse=True)


def reverse_len(s):
    return -len(s)


sorted(animals, key=reverse_len)

图片图片

正如您可以将一个函数作为参数传递给另一个函数一样,函数也可以指定另一个函数作为其返回值:

def outer():
    def inner():
        print("I am function inner()!")
    # Function outer() returns function inner()
    return inner


function = outer()
print( function )


function()


outer()()

图片图片

在这个示例中发生的过程如下:

  • 第 2 到 3 行:outer() 定义了一个局部函数 inner()。
  • 第 5 行:outer() 将 inner() 作为返回值返回。
  • 第 87行:您将 outer() 的返回值赋给变量 function。
  • 之后,您可以通过 function 间接调用 inner(),如第 10 行所示。也可以通过 outer() 的返回值直接调用 inner(),如第 12 行所示,无需中间赋值。

如您所见,Python 拥有支持函数式编程的所有必要组件。但在深入函数式代码之前,还有一个概念很有帮助,那就是lambda 表达式。

使用 lambda 定义匿名函数

函数式编程的核心是调用和传递函数,因此通常涉及大量的函数定义。您可以像往常一样使用 def 关键字定义函数。

有时,能够在不需要给函数命名的情况下定义一个匿名函数会很方便。在 Python 中,您可以使用 lambda 表达式来实现这一点。

lambda 表达式的语法如下:

lambda <parameter_list>: <expression>

以下表格总结了 lambda 表达式的各个部分:

组件

说明

lambda

引入 lambda 表达式的关键字

<parameter_list>

可选的用逗号分隔的参数名称列表

:

标点符号,用于分隔 <parameter_list> 和 <expression>

<expression>

通常涉及 <parameter_list> 中名称的表达式,表示 lambda 函数的返回值

lambda 表达式的值是一个可调用的函数,类似于使用 def 关键字定义的函数。它接受由 <parameter_list> 指定的参数,并返回由 <expression> 指定的值。

以下是一个简单的示例:

图片图片

第 1 行的语句只是 lambda 表达式本身。

内置的 Python 函数 callable() 如果传递给它的参数看起来是可调用的,则返回 True,否则返回 False。第 3 行显示了 lambda 表达式返回的值实际上是可调用的,就像一个函数应该的那样。

在这个例子中,参数列表包含一个参数 s。随后的表达式 s[::-1] 是切片语法,用于以相反的顺序返回 s 中的字符。因此,这个 lambda 表达式定义了一个临时的无名函数,它接受一个字符串参数并返回字符顺序颠倒的字符串。

由 lambda 表达式创建的对象是第一类公民,就像标准函数或 Python 中的任何其他对象一样。您可以将其赋值给一个变量,然后使用该名称调用函数:

reverse = lambda s: s[::-1]
reverse("I am a string")

图片图片

然而,在调用 lambda 表达式定义的函数之前,您不一定需要将其赋值给一个变量。您也可以直接调用由 lambda 表达式定义的函数:

(lambda s: s[::-1])("I am a string")

图片图片

您将 lambda 表达式括在括号中以明确其结束位置,然后添加了一组括号,并将 "I am a string" 作为参数传递给您的匿名函数。Python 将字符串参数分配给参数 s,然后您的 lambda 函数反转了字符串并返回结果。

这是另一个示例,基于相同的概念,但因为在 lambda 表达式中使用了多个参数,所以更加复杂:

a= (lambda x1, x2, x3: (x1 + x2 + x3) / 3)(9, 6, 6)
print(a)
(lambda x1, x2, x3: (x1 + x2 + x3) / 3)(1.4, 1.1, 0.5)

图片图片

在这个例子中,参数是 x1、x2 和 x3,表达式是 x1 + x2 + x3 / 3。这是一个匿名 lambda 函数,用于计算三个数字的平均值。

使用 lambda 表达式的真正优势在于它们适用于短小而直接的逻辑。您可以用一个简洁直接的 lambda 表达式来代替定义 reverse_len:

图片图片

lambda 表达式通常会有一个参数列表,但这不是必须的。您可以定义一个没有参数的 lambda 函数。在这种情况下,返回值不依赖于任何输入参数:

图片图片

lambda 只能用于定义比较简单的函数。lambda 表达式的返回值只能是一个单一的表达式。lambda 表达式不能包含诸如赋值或 return 的语句,也不能包含控制结构,如 for、while、if、else 或 def。

lambda 函数在编写函数式代码时特别方便。Python 提供了两个内置函数 map() 和 filter(),它们符合函数式编程范式。第三个函数 reduce() 不再是核心语言的一部分,但仍然可以在名为 functools 的模块中使用。这三个函数中的每一个都将另一个函数作为其参数之一。

使用 map() 对可迭代对象应用函数

第一个要介绍的函数是 map(),这是 Python 的一个内置函数。使用 map(),您可以依次将一个函数应用于可迭代对象中的每个元素。map() 函数将返回一个迭代器,该迭代器生成结果。这可以使代码变得非常简洁,因为 map() 语句通常可以替代显式的循环。

您可以使用一个可迭代对象或多个可迭代对象来调用 map()。接下来,您将查看在单个可迭代对象上调用 map() 的语法。

map(<f>, <iterable>) 返回一个迭代器,该迭代器生成将函数 <f> 应用到 <iterable> 中每个元素的结果。

下面是一个示例。假设您已经定义了 reverse() 函数,该函数接受一个字符串参数,并使用旧友 [::-1] 字符串切片机制返回其反转结果;如果您有一个字符串列表,可以使用 map() 将 reverse() 应用到列表中的每个元素:

def reverse(s):
    return s[::-1]


print( reverse("I am a string") )


animals = ["cat", "dog", "hedgehog", "gecko"]
print( map(reverse, animals) )
list( map(reverse, animals) )

图片图片

map() 不会返回一个列表。它返回的是一个 map 对象,这是一个迭代器。

使用多个可迭代对象调用 map()

另一种使用 map() 的方式是,当您在函数参数后传递多个可迭代对象时:

map(<f>, <iterable₁>, <iterable₂>, ..., <iterableₙ>)

在这个例子中,map(<f>, <iterable1>, <iterable2>, ..., <iterablen>) 会将 <f> 应用到每个 <iterablei> 中的元素,并且以并行的方式返回一个迭代器,生成结果。

传递给 map() 的 <iterablei> 参数的数量必须与 <f> 预期的参数数量匹配。<f> 作用于每个 <iterablei> 的第一个项目,生成的结果成为返回迭代器的第一个生成项。然后,<f> 作用于每个 <iterablei> 的第二个项目,生成的结果成为第二个生成项,以此类推。

一个详细的示例可以帮助您更清楚地理解:

def add_three(a, b, c):
    return a + b + c


list(map(add_three, [1, 2, 3], [10, 20, 30], [100, 200, 300]))

图片图片

第一项是计算 add_three(1, 10, 100),第二项是计算 add_three(2, 20, 200) 的结果,第三项是计算 add_three(3, 30, 300) 的结果。这可以通过以下示意图来表示:

图片图片

使用 filter() 从可迭代对象中选择元素

filter() 允许您根据给定函数的评估来选择或过滤可迭代对象中的项。其函数如下:

filter(<f>, <iterable>)

filter(<f>, <iterable>) 将函数 <f> 应用到 <iterable> 中的每个元素,并返回一个迭代器,该迭代器生成所有 <f> 结果为真值的项。相反,它会过滤掉所有 <f> 结果为假值的项。

在以下示例中,如果 x > 100,greater_than_100(x) 就会返回真值:

def greater_than_100(x):
    return x > 100


list(filter(greater_than_100, [1, 111, 2, 222, 3, 333]))

图片图片

在这种情况下,greater_than_100() 对项 111、222 和 333 产生真值,因此这些项会保留,而 filter() 会丢弃 1、2 和 3。与之前的示例一样,greater_than_100() 是一个简短的函数,您可以用 lambda 表达式替代它:

图片图片

使用 reduce() 将可迭代对象归约为单一值

reduce() 将一个函数应用于可迭代对象中的项,每次两个项一同处理,逐步合并它们以生成一个最终结果。

最直接的reduce()调用需要一个函数和一个可迭代对象:

reduce(<f>,<iterable>)

在调用 时reduce(<f>, <iterable>),函数<f>必须是采用两个参数的函数。然后将使用reduce()逐步组合 中的元素。首先,对 的前两个元素调用。然后将该结果与第三个元素组合,然后将该结果与第四个元素组合,依此类推,直到列表用尽。然后,返回最终结果。

def f(x, y):
    return x + y


from functools import reduce
reduce(f, [1, 2, 3, 4, 5])

图片图片

此调用将产生列表的reduce()结果,如下所示:

图片图片

这是对列表中的数字求和的一种相当迂回的方法。

函数式编程是一种编程范式,其中主要的计算方法是纯函数的求值。尽管 Python 主要不是函数式语言,但您仍然可以按照函数式编程原则编写 Python。最好熟悉lambda、map()、filter()和reduce()。它们可以帮助您编写简洁、高级、可并行的代码。您可能还会在其他人编写的代码中看到这些函数的使用,因此了解它们的工作原理是很好的。

责任编辑:武晓燕 来源: 新语数据故事汇
相关推荐

2024-07-11 12:14:20

Pythonmapfilter

2024-10-06 14:01:47

Python装饰器对象编程

2019-08-06 09:00:00

JavaScript函数式编程前端

2024-01-10 08:47:48

Python函数Map()

2024-05-21 09:45:40

机器学习人工智能XAI

2022-07-19 15:24:45

Python编程技术

2024-10-17 16:45:46

Python内置函数

2024-10-08 10:44:32

2024-07-31 15:11:57

SymPypython数学运算

2023-11-22 16:10:59

编程语言机器语言

2024-06-04 00:20:00

Python函数

2024-03-08 09:45:21

Lambda表达式Stream

2023-12-26 01:14:20

函数式编程死锁

2010-10-09 14:00:10

mysql CONCA

2010-10-25 15:04:39

Oracle文本函数

2024-03-12 17:54:55

容器类型Init

2024-11-08 12:42:34

Rustmapfilter

2023-05-31 13:32:08

Javalambda函数

2023-07-31 07:25:27

2024-05-13 11:25:08

概念模型逻辑模型物理模型
点赞
收藏

51CTO技术栈公众号