编程技术开发和娱乐网址导航

网站首页 > 技术文章 正文

一文带您了解Python的函数式编程:理解lambd、map、filter

luoxia7 2024-10-13 06:46:49 技术文章 2 ℃ 0 评论

函数式编程在数据处理领域中扮演着重要的角色,其优势在于能以简洁和直观的方式处理和转换数据。通过将数据转换操作封装在纯函数中,函数式编程避免了副作用和可变状态,提升了代码的可维护性和可读性。在处理数据时,函数式编程提供了强大的工具,如 lambd、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。随后,您可以通过 funcanother_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)

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

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

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

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

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 > 100greater_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。最好熟悉lambdamap()filter()reduce()。它们可以帮助您编写简洁、高级、可并行的代码。您可能还会在其他人编写的代码中看到这些函数的使用,因此了解它们的工作原理是很好的。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表