网站首页 > 技术文章 正文
函数式编程在数据处理领域中扮演着重要的角色,其优势在于能以简洁和直观的方式处理和转换数据。通过将数据转换操作封装在纯函数中,函数式编程避免了副作用和可变状态,提升了代码的可维护性和可读性。在处理数据时,函数式编程提供了强大的工具,如 lambd、map()、filter() 和 reduce(),这些工具允许开发者高效地应用操作、筛选和归约数据集合。利用这些函数,数据处理可以变得更加简洁、模块化。这种编程范式不仅有助于编写更清晰的代码,还能帮助开发者应对复杂的数据处理任务,实现更高效的数据流转和分析。
什么是函数式编程?
纯函数是指输出值完全由输入值决定,并且没有任何可观察的副作用的函数。在函数式编程中,程序主要由纯函数的计算组成。计算通过嵌套或组合的函数调用进行,而不会改变状态或可变数据。
函数式编程范式之所以受欢迎,是因为它相对于其他编程范式有几个优势。函数式代码具有以下特点:
- 高级抽象:您描述的是想要的结果,而不是明确指定如何一步步达成这个结果。单个语句通常简洁但功能强大。
- 透明性:纯函数的行为可以通过其输入和输出来描述,而无需依赖中间值。这消除了副作用的可能性,并有助于调试。
- 并行化:不引发副作用的例程可以更容易地彼此并行运行。
许多编程语言都在一定程度上支持函数式编程。在某些语言中,几乎所有代码都遵循函数式编程范式。Haskell 就是这样的例子。而 Python 则同时支持函数式编程和其他编程模型。
虽然函数式编程的详细描述确实较为复杂,但这里的目标并不是提供严格的定义,而是展示如何在 Python 中进行函数式编程。
Python 对函数式编程的支持如何?
为了支持函数式编程,如果一种编程语言中的函数能够做到以下两点,将会非常有利:
- 接受另一个函数作为参数
- 返回另一个函数给调用者
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-10-13 jdbc完美封装-传入类型返回bean或者list集合
- 2024-10-13 JS数组和对象相互转换方法「实用」
- 2024-10-13 ES6 新特征之Map es6 map使用场景
- 2024-10-13 JavaScript:ES中的对象属性、Set、Map与对象拷贝
- 2024-10-13 Cpp浅析系列-STL之map stl中map
- 2024-10-13 Python三个常用高阶函数map,filter,reduc)和闭包递归匿名及改名
- 2024-10-13 MapStruct与Lombok一起使用的兼容性问题
- 2024-10-13 腾讯大牛用10分钟教会我,Java遍历Map集合,不服不行
- 2024-10-13 jackson 解決转义字符的问题 jackson转字符串
- 2024-10-13 json和map对象中的get方法 json map区别
你 发表评论:
欢迎- 最近发表
-
- IntelliJ IDEA必备7款Python插件 idea运行python文件
- IntellIJ IDEA 命令行运行一个Python 程序
- idea搭建python环境,tensorFlow样例程序
- 使用IDEA写Python之pytest环境搭建及...
- Python二级(01)——用IDEA开发python,就是好这么酸爽
- IDEA中配置Python环境并运行 idea编写python代码
- 超哥带你体验angularJS angularjs官网
- 将Web仪表盘添加到Angular CLI应用,一键GET
- 前端程序员必知:单页面应用的核心
- AngularJS是什么? angular jsx
- 标签列表
-
- request.getheader (57)
- pomexclusion (55)
- javassh连接远程服务器 (80)
- java-jar输出日志 (71)
- mybatis.type-aliases-package (63)
- arraystoreexception (56)
- 线程池submit (67)
- vue2props (57)
- 微信小程序form表单提交 (55)
- javabase64转图片 (55)
- javaobject转map (74)
- tcpflags (59)
- 虚拟机共享文件夹在哪 (60)
- shiro设置session过期时间 (61)
- resttemplate文件上传 (57)
- networkmanager是什么服务 (69)
- oauthservice (56)
- idea修改git地址 (79)
- file.encoding (62)
- swaggerlist (58)
- datetime.time (57)
- pythonthreadpoolexecutor (63)
- e.printstacktrace() (65)
- 什么意思 (65)
- jar包启动命令 (56)
本文暂时没有评论,来添加一个吧(●'◡'●)