Python 的 reduce():从函数式到 Pythonic 风格
目录
Pythonreduce()
是一个函数,它实现了一种称为折叠或缩减的数学技术。reduce()
当您需要将函数应用于可迭代对象并将其减少为单个累积值时非常有用。Pythonreduce()
在具有函数式编程背景的开发人员中很受欢迎,但 Python 可以提供更多。
在本教程中,您将介绍它的reduce()
工作原理以及如何有效地使用它。您还可以涵盖一些替代Python的工具,可以将更多的Python化,可读性和效率比reduce()
。
在本教程中,您将学习:
- Python 的
reduce()
工作原理 - 什么是较常见的还原用例是
- 如何使用解决这些用例
reduce()
- 哪些替代 Python 工具可用于解决这些相同的用例
有了这些知识,您将能够决定在解决 Python 中的归约或折叠问题时使用哪些工具。
为了更好地理解Python的的reduce()
,这将是有帮助的一些以前的知识如何与工作的Python iterables,尤其是如何循环使用它们一for
环。
探索 Python 中的函数式编程
函数式编程是一种基于将问题分解为一组单独函数的编程范式。理想情况下,每个函数只接受一组输入参数并产生输出。
在函数式编程中,函数没有任何影响它们为给定输入产生的输出的内部状态。这意味着无论何时您使用相同的一组输入参数调用函数,您都会得到相同的结果或输出。
在函数式程序中,输入数据流经一组函数。每个函数对其输入进行操作并产生一些输出。函数式编程尽量避免可变数据类型和状态更改。它适用于在函数之间流动的数据。
函数式编程的其他核心特性包括:
这个列表中有几个重要的概念。以下是其中一些的详细信息:
-
递归是一种技术,其中函数直接或间接调用自身以进行循环。它允许程序循环遍历长度未知或不可预测的数据结构。
-
纯函数是完全没有副作用的函数。换句话说,它们是不更新或修改程序中的任何全局变量、对象或数据结构的函数。这些函数产生的输出仅取决于输入,这更接近于数学函数的概念。
-
高阶函数是通过将函数作为参数、返回函数或两者兼有来对其他函数进行操作的函数,就像Python 装饰器一样。
由于 Python 是一种多范式编程语言,它提供了一些支持函数式编程风格的工具:
- 函数作为一等对象
- 递归能力
- 匿名函数
lambda
- 迭代器和生成器
- 标准模块,如
functools
和itertools
- 工具喜欢
map()
,filter()
,reduce()
,sum()
,len()
,any()
,all()
,min()
,max()
,等
尽管 Python并未受到函数式编程语言的严重影响,但早在 1993 年,对上面列出的一些函数式编程功能的需求就很明确。
作为回应,该语言中添加了几个功能性工具。根据Guido van Rossum 的说法,它们是由社区成员贡献的:
Python的收购
lambda
,reduce()
,filter()
和map()
中,(我相信)Lisp的黑客谁错过了他们的礼貌和提交工作补丁。(来源)
多年来,新功能,如列表内涵,发电机表情,和内置的功能,如sum()
,min()
,max()
,all()
,和any()
被视为Python的替代品map()
,filter()
和reduce()
。Guido计划 从 Python 3 中的语言中删除map()
, filter()
, reduce()
, 甚至lambda
。
幸运的是,这次移除并没有生效,主要是因为 Python 社区不想放弃这些流行的功能。它们仍然存在并且仍然在具有强大函数式编程背景的开发人员中广泛使用。
在本教程中,您将介绍如何在reduce()
不使用for
循环的情况下使用 Python处理可迭代对象并将它们减少为单个累积值。您还将了解一些 Python 工具,您可以使用这些工具reduce()
来使您的代码更具 Python 风格、可读性和效率。
Python 入门 reduce()
Pythonreduce()
实现了一种通常称为折叠或缩减的数学技术。当您将项目列表减少到单个累积值时,您正在执行折叠或减少。Pythonreduce()
对任何可迭代对象(不仅仅是列表)进行操作,并执行以下步骤:
- 将函数(或可调用)应用于可迭代对象中的前两项并生成部分结果。
- 使用该部分结果以及可迭代对象中的第三项,生成另一个部分结果。
- 重复这个过程直到迭代用完,然后返回一个累积值。
Python 背后的想法reduce()
是采用现有函数,将其累积应用于迭代中的所有项目,并生成单个最终值。通常,Pythonreduce()
可以方便地处理迭代,而无需编写显式for
循环。由于reduce()
是用 C 编写的,它的内部循环可以比显式 Pythonfor
循环更快。
Python的reduce()
原本是内置函数(现在仍然是Python的2.X),但它被转移到functools.reduce()
在Python的3.0。这个决定是基于一些可能的性能和可读性问题。
另一个原因,用于移动reduce()
到functools
被引入的内置函数喜欢sum()
,any()
,all()
,max()
,min()
,和len()
,其中提供处理共同使用情况的更有效的,可读的,和Python化方式reduce()
。reduce()
在本教程的后面部分,您将学习如何使用它们。
在 Python 3.x 中,如果您需要使用reduce()
,那么您首先必须通过以下方式之一使用语句将函数导入当前范围:import
import functools
然后使用完全限定的名称,如functools.reduce()
.from functools import reduce
然后reduce()
直接调用。
根据该文件的reduce()
,该功能具有以下特征:
functools.reduce(function, iterable[, initializer])
Python 文档还指出,reduce()
它大致相当于以下 Python 函数:
def reduce(function, iterable, initializer=None):
it = iter(iterable)
if initializer is None:
value = next(it)
else:
value = initializer
for element in it:
value = function(value, element)
return value
像这个 Python 函数一样,它reduce()
通过iterable
从左到右将两个参数的函数应用于循环中的项目,最终减少iterable
到单个累积value
.
Pythonreduce()
还接受第三个可选参数initializer
,该参数为计算或减少提供种子值。
在接下来的两节中,您将深入了解 Python 的reduce()
工作原理及其每个参数背后的含义。
所需的参数:function
和iterable
Python 的第一个参数reduce()
是一个双参数函数,方便地称为function
. 此函数将应用于迭代中的项目以累积计算最终值。
尽管官方文档将 的第一个参数reduce()
称为“具有两个参数的函数”,但您可以传递任何 Python 可调用对象,reduce()
只要该可调用对象接受两个参数即可。可调用对象包括类、实现称为 的特殊方法的__call__()
实例、实例方法、类方法、静态方法和函数。
注意:有关 Python 可调用对象的更多详细信息,您可以查看 Python文档并向下滚动到“可调用类型”。
iterable
顾名思义,第二个必需参数将接受任何 Python 可迭代对象。这包括列表、元组、range
对象、生成器、迭代器、集合、字典键和值,以及您可以迭代的任何其他 Python 对象。
注意:如果您将迭代器传递给 Python 的reduce()
,则该函数将需要耗尽迭代器才能获得最终值。因此,手头的迭代器不会保持惰性。
要了解如何reduce()
工作,你会写,计算两者之和功能的数字和打印相当于数学运算到屏幕上。这是代码:
>>> def my_add(a, b):
... result = a + b
... print(f"{a} + {b} = {result}")
... return result
此函数计算a
and的总和,b
使用f-string打印带有操作的消息,并返回计算结果。这是它的工作原理:
>>> my_add(5, 5)
5 + 5 = 10
10
my_add()
是一个双参数函数,因此您可以将它reduce()
与可迭代对象一起传递给 Python ,以计算可迭代对象中各项的累积总和。查看以下使用数字列表的代码:
>>> from functools import reduce
>>> numbers = [0, 1, 2, 3, 4]
>>> reduce(my_add, numbers)
0 + 1 = 1
1 + 2 = 3
3 + 3 = 6
6 + 4 = 10
10
当您调用reduce()
、传递my_add()
和numbers
作为参数时,您会得到一个输出,其中显示了reduce()
为得出最终结果而执行的所有操作10
。在这种情况下,操作等效于((((0 + 1) + 2) + 3) + 4) = 10
。
上例中的 调用reduce()
适用my_add()
于numbers
(0
和1
) 中的前两项,并1
作为结果获取。然后reduce()
调用my_add()
using1
和numbers
(即2
)中的下一项作为参数,得到3
结果。重复该过程,直到numbers
用完项目并reduce()
返回 的最终结果10
。
可选参数: initializer
Python 的第三个参数reduce()
,称为initializer
,是可选的。如果您向 提供值initializer
,reduce()
则将其function
作为第一个参数提供给第一次调用。
这意味着第一次调用function
将使用的值initializer
和第一项iterable
来执行它的第一部分计算。在此之后,reduce()
继续处理 的后续项目iterable
。
下面是你用一个例子my_add()
与initializer
设置为100
:
>>> from functools import reduce
>>> numbers = [0, 1, 2, 3, 4]
>>> reduce(my_add, numbers, 100)
100 + 0 = 100
100 + 1 = 101
101 + 2 = 103
103 + 3 = 106
106 + 4 = 110
110
由于您提供了100
to的值initializer
,Pythonreduce()
在第一次调用中使用该值作为 的第一个参数my_add()
。请注意,在第一次迭代中,my_add()
使用100
和0
作为 的第一项numbers
来执行计算100 + 0 = 100
。
另一点要注意的是,如果您为 提供值initializer
,则将reduce()
比没有 时多执行一次迭代initializer
。
如果您打算使用reduce()
处理可能为空的可迭代对象,那么为 提供值是一种很好的做法initializer
。reduce()
当iterable
为空时,Python将使用此值作为其默认返回值。如果您不提供initializer
值,reduce()
则将引发TypeError
. 看看下面的例子:
>>> from functools import reduce
>>> # Using an initializer value
>>> reduce(my_add, [], 0) # Use 0 as return value
0
>>> # Using no initializer value
>>> reduce(my_add, []) # Raise a TypeError with an empty iterable
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: reduce() of empty sequence with no initial value
如果您使用空调reduce()
用iterable
,则该函数将返回提供给 的值initializer
。如果您不提供 an initializer
,则在处理空可迭代对象时reduce()
将引发 a TypeError
。
注意:要深入了解 Python 回溯是什么,请查看了解 Python 回溯。
现在您已经熟悉了reduce()
工作原理,您已经准备好学习如何将其应用于一些常见的编程问题。
使用 Python 减少可迭代对象 reduce()
到目前为止,您已经了解了 Python 的reduce()
工作原理以及如何使用它来减少使用用户定义函数的可迭代对象。您还了解了每个参数的含义reduce()
以及它们的工作原理。
在本节中,您将了解一些常见用例reduce()
以及如何使用该函数解决它们。您还将了解一些可替代的 Python 工具,您可以使用它们reduce()
来使您的代码更加 Python 化、高效和可读。
对数值求和
在"Hello, World!"
Python的的reduce()
是和使用情况。它涉及计算数字列表的累积总和。假设您有一个数字列表,例如[1, 2, 3, 4]
. 它的总和将为1 + 2 + 3 + 4 = 10
。以下是如何使用 Pythonfor
循环解决此问题的快速示例:
>>> numbers = [1, 2, 3, 4]
>>> total = 0
>>> for num in numbers:
... total += num
...
>>> total
10
该for
循环遍历每个值numbers
并累加它们total
。最终结果是所有值的总和,在本例中为10
。在这个例子中使用的变量total
有时称为累加器。
这可以说是 Python 的reduce()
. 要使用 实现此操作reduce()
,您有多种选择。其中一些包括reduce()
与以下功能之一一起使用:
- 甲用户定义函数
- 一个
lambda
函数 - 一个函数调用
operator.add()
要使用用户定义的函数,您需要编写一个将两个数字相加的函数。然后您可以将该功能与reduce()
. 对于此示例,您可以my_add()
按如下方式重写:
>>> def my_add(a, b):
... return a + b
...
>>> my_add(1, 2)
3
my_add()
将两个数字a
和相加b
,并返回结果。使用my_add()
到位,您可以使用reduce()
来计算 Python 可迭代对象中的值的总和。就是这样:
>>> from functools import reduce
>>> numbers = [1, 2, 3, 4]
>>> reduce(my_add, numbers)
10
调用reduce()
应用于my_add()
中的项目numbers
以计算它们的累积总和。最终的结果是10
,正如预期的那样。
您还可以使用lambda
函数执行相同的计算。在这种情况下,您需要一个lambda
将两个数字作为参数并返回它们总和的函数。看看下面的例子:
>>> from functools import reduce
>>> numbers = [1, 2, 3, 4]
>>> reduce(lambda a, b: a + b, numbers)
10
该lambda
函数接受两个参数并返回它们的总和。在循环中reduce()
应用该lambda
函数来计算 中项目的累积总和numbers
。
同样,您可以利用名为operator
. 该模块导出了一系列与 Python 的内在运算符相对应的函数。对于手头的问题,您可以operator.add()
与 Python 的reduce()
. 查看以下示例:
>>> from operator import add
>>> from functools import reduce
>>> add(1, 2)
3
>>> numbers = [1, 2, 3, 4]
>>> reduce(add, numbers)
10
在这个例子中,add()
接受两个参数并返回它们的总和。因此,您可以使用add()
withreduce()
来计算 的所有项目的总和numbers
。由于add()
是用 C 编写的并针对效率进行了优化,因此在reduce()
用于解决 sum 用例时,它可能是您的最佳选择。请注意,使用 ofoperator.add()
也比使用lambda
函数更具可读性。
sum 用例在编程中非常普遍,以至于 Python 从2.3 版开始就包含了一个专用的内置函数sum()
来解决它。sum()
被声明为sum(iterable[, start])
.
start
是一个可选参数sum()
,默认为0
。该函数将 的值与从左到右start
的项目相加iterable
并返回总数。看看下面的例子:
>>> numbers = [1, 2, 3, 4]
>>> sum(numbers)
10
由于sum()
是内置函数,因此您无需导入任何内容。它随时可供您使用。Usingsum()
是解决 sum 用例的最 Pythonic 的方法。它干净、易读且简洁。它遵循 Python 的核心原则:
简单胜于复杂。(来源)
sum()
与使用reduce()
或for
循环相比,添加到语言中在可读性和性能方面是一个巨大的胜利。
注意:有关将 的性能reduce()
与其他 Python 缩减工具的性能进行比较的更多详细信息,请查看性能是关键部分。
如果您正在处理 sum 用例,那么好的做法建议使用sum()
.
乘以数值
该产品使用情况Python的的reduce()
是神似和使用情况,但此时的操作是乘法。换句话说,您需要计算可迭代对象中所有值的乘积。
例如,假设您有 list [1, 2, 3, 4]
。其产品将1 * 2 * 3 * 4 = 24
。您可以使用 Pythonfor
循环来计算它。查看以下示例:
>>> numbers = [1, 2, 3, 4]
>>> product = 1
>>> for num in numbers:
... product *= num
...
>>> product
24
循环遍历 中的项目numbers
,将每个项目乘以上一次迭代的结果。在这种情况下,累加器的起始值product
应该是1
而不是0
。由于任何乘以零的数字为零,因此 的起始值0
将始终使您的乘积等于0
。
这种计算也是 Python 的reduce()
. 同样,您将介绍解决问题的三种方法。您将使用reduce()
:
- 用户定义的函数
- 一个
lambda
函数 - 一个函数调用
operator.mul()
对于选项 1,您需要编写一个自定义函数,该函数接受两个参数并返回它们的乘积。然后,您将使用此函数 withreduce()
来计算可迭代对象中项目的乘积。看看下面的代码:
>>> from functools import reduce
>>> def my_prod(a, b):
... return a * b
...
>>> my_prod(1, 2)
2
>>> numbers = [1, 2, 3, 4]
>>> reduce(my_prod, numbers)
24
该函数my_prod()
将两个数字相乘,a
并且b
。调用reduce()
迭代 的项目numbers
并通过应用于my_prod()
连续项目来计算它们的乘积。最终结果是 中所有项的乘积numbers
,在本例中为24
。
如果您更喜欢使用lambda
函数来解决这个用例,那么您需要一个接受两个参数并返回它们的乘积的函数。下面是一个例子:
>>> from functools import reduce
>>> numbers = [1, 2, 3, 4]
>>> reduce(lambda a, b: a * b, numbers)
24
匿名函数通过在reduce()
迭代 时将连续项相乘来实现魔术numbers
。同样,结果是 中所有项目的乘积numbers
。
您还可以operator.mul()
用于处理产品用例。operator.mul()
接受两个数字并返回它们相乘的结果。这是解决手头问题的正确功能。查看以下示例:
>>> from operator import mul
>>> from functools import reduce
>>> mul(2, 2)
4
>>> numbers = [1, 2, 3, 4]
>>> reduce(mul, numbers)
24
由于mul()
高度优化,如果您使用此函数而不是用户定义的函数或lambda
函数,您的代码将执行得更好。请注意,此解决方案也更具可读性。
最后,如果您使用的是Python 3.8,那么您可以访问此用例的更 Pythonic 和可读的解决方案。Python 3.8 添加了一个名为 的新函数prod()
,它位于Pythonmath
模块中。此函数类似于sum()
但返回一个start
值乘以一个iterable
数字的乘积。
在 的情况下math.prod()
,参数start
是可选的,默认为1
。这是它的工作原理:
>>> from math import prod
>>> numbers = [1, 2, 3, 4]
>>> prod(numbers)
24
与使用reduce()
. 因此,如果您使用的是Python 3.8并且产品归约是您代码中的常见操作,那么使用math.prod()
而不是 Python 的reduce()
.
寻找最小值和最大值
在可迭代对象中找到最小值和最大值的问题也是一个归约问题,您可以使用 Python 的reduce()
. 这个想法是比较迭代中的项目以找到最小值或最大值。
假设您有数字列表[3, 5, 2, 4, 7, 1]
。在此列表中,最小值为1
,最大值为7
。要查找这些值,您可以使用 Pythonfor
循环。查看以下代码:
>>> numbers = [3, 5, 2, 4, 7, 1]
>>> # Minimum
>>> min_value, *rest = numbers
>>> for num in rest:
... if num < min_value:
... min_value = num
...
>>> min_value
1
>>> # Maximum
>>> max_value, *rest = numbers
>>> for num in rest:
... if num > max_value:
... max_value = num
...
>>> max_value
7
两个循环都迭代 in 中的项目rest
并根据连续比较的结果更新min_value
或的值max_value
。请注意,最初min_value
并max_value
保持数字3
,它是 中的第一个值numbers
。该变量rest
将剩余的值保存在numbers
. 换句话说,rest = [5, 2, 4, 7, 1]
。
注:在上面的例子中,你使用Python迭代拆包经营者(*
)来解压或扩展中的值numbers
分为两个变量。在第一种情况下,净效果是min_value
获取 中的第一个值numbers
,即3
,并rest
收集列表中的其余值。
查看以下示例中的详细信息:
>>> numbers = [3, 5, 2, 4, 7, 1]
>>> min_value, *rest = numbers
>>> min_value
3
>>> rest
[5, 2, 4, 7, 1]
>>> max_value, *rest = numbers
>>> max_value
3
>>> rest
[5, 2, 4, 7, 1]
*
当您需要将一个序列或可迭代对象解包为多个变量时,Python 可迭代解包运算符 ( ) 非常有用。
为了更好地理解 Python 中的解包操作,您可以查看PEP 3132 Extended Iterable Unpacking和PEP 448 Additional Unpacking Generalizations。
现在,考虑如何使用 Python 的reduce()
. 同样,您可以lambda
根据需要使用用户定义的函数或函数。
以下代码实现了一个使用两个不同用户定义函数的解决方案。第一个函数将接受两个参数a
和b
,并返回它们的最小值。第二个函数将使用类似的过程,但它会返回最大值。
以下是函数以及如何将它们与 Pythonreduce()
一起使用以查找可迭代对象中的最小值和最大值:
>>> from functools import reduce
>>> # Minimum
>>> def my_min_func(a, b):
... return a if a < b else b
...
>>> # Maximum
>>> def my_max_func(a, b):
... return a if a > b else b
...
>>> numbers = [3, 5, 2, 4, 7, 1]
>>> reduce(my_min_func, numbers)
1
>>> reduce(my_max_func, numbers)
7
当您reduce()
使用my_min_func()
和运行时my_max_func()
,您将numbers
分别获得 中的最小值和最大值。reduce()
迭代 的项numbers
,按累积对比较它们,最后返回最小值或最大值。
注意:为了实现my_min_func()
and my_max_func()
,您使用了 Python 条件表达式或三元运算符作为return
值。要更深入地了解什么是条件表达式及其工作原理,请查看Python 中的条件语句 (if/elif/else)。
您还可以使用lambda
函数来解决最小值和最大值问题。看看下面的例子:
>>> from functools import reduce
>>> numbers = [3, 5, 2, 4, 7, 1]
>>> # Minimum
>>> reduce(lambda a, b: a if a < b else b, numbers)
1
>>> # Maximum
>>> reduce(lambda a, b: a if a > b else b, numbers)
7
这一次,您使用两个lambda
函数来确定 ifa
小于或大于b
。在这种情况下,Pythonreduce()
将lambda
函数应用于 中的每个值numbers
,并将其与先前计算的结果进行比较。在过程结束时,您将获得最小值或最大值。
最小和最大问题在编程中非常普遍,以至于 Python 添加了内置函数来执行这些缩减。这些函数被方便地称为min()
and max()
,您无需导入任何内容即可使用它们。以下是它们的工作原理:
>>> numbers = [3, 5, 2, 4, 7, 1]
>>> min(numbers)
1
>>> max(numbers)
7
当您使用min()
andmax()
查找可迭代对象中的最小和最大项时,与使用 Python 的reduce()
. 此外,由于min()
和max()
是高度优化的 C 函数,您也可以说您的代码将更加高效。
所以,在 Python 中解决这个问题时,最好使用min()
andmax()
而不是reduce()
.
检查所有值是否为真
该全真用例Python的中reduce()
涉及找出在是否所有的项目迭代是真实的。要解决这个问题,您可以reduce()
与用户定义的函数或lambda
函数一起使用。
您将首先编写一个for
循环来确定迭代中的所有项目是否都为真。这是代码:
>>> def check_all_true(iterable):
... for item in iterable:
... if not item:
... return False
... return True
...
>>> check_all_true([1, 1, 1, 1, 1])
True
>>> check_all_true([1, 1, 1, 1, 0])
False
>>> check_all_true([])
True
如果 中的所有值iterable
都为真,则check_all_true()
返回True
。否则,它返回False
。它还返回True
空的可迭代对象。check_all_true()
实施短路评估。这意味着该函数会在不处理 中的其余项的情况下,在发现错误值时立即返回iterable
。
要使用 Python 的 解决这个问题reduce()
,您需要编写一个函数,该函数接受两个参数并True
在两个参数都为真时返回。如果一个或两个参数为假,则函数将返回False
。这是代码:
>>> def both_true(a, b):
... return bool(a and b)
...
>>> both_true(1, 1)
True
>>> both_true(1, 0)
False
>>> both_true(0, 0)
False
这个函数有两个参数,a
和b
。然后您使用and
运算符来测试两个参数是否都为真。True
如果两个参数都为真,则返回值。否则,它将是False
。
在 Python 中,以下对象被认为是 false:
- 像
None
和这样的常数False
- 数值类型与零值一样
0
,0.0
,0j
,Decimal(0)
,和Fraction(0, 1)
- 空序列和收藏喜欢
""
,()
,[]
,{}
,set()
,和range(0)
- 实现物体
__bool__()
与返回值False
或__len__()
与返回值0
任何其他对象都将被视为真实。
您需要使用bool()
将 的返回值and
转换为True
或False
。如果您不使用bool()
,那么您的函数将不会按预期运行,因为and
返回表达式中的对象之一而不是True
or False
。查看以下示例:
>>> a = 0
>>> b = 1
>>> a and b
0
>>> a = 1
>>> b = 2
>>> a and b
2
and
如果为假,则返回表达式中的第一个值。否则,无论其真值如何,它都会返回表达式中的最后一个值。这就是为什么你需要bool()
在这种情况下使用。bool()
返回由计算布尔表达式或对象产生的布尔值(True
或False
)。使用bool()
以下方法查看示例:
>>> a = 0
>>> b = 1
>>> bool(a and b)
False
>>> a = 1
>>> b = 2
>>> bool(a and b)
True
bool()
将始终返回True
或False
在评估手头的表达式或对象之后返回。
注:为了更好地理解Python的运算符和表达式,你可以看看在Python运算符和表达式。
您可以通过both_true()
toreduce()
检查迭代的所有项目是否为真。这是它的工作原理:
>>> from functools import reduce
>>> reduce(both_true, [1, 1, 1, 1, 1])
True
>>> reduce(both_true, [1, 1, 1, 1, 0])
False
>>> reduce(both_true, [], True)
True
如果您将both_true()
参数作为参数传递给reduce()
,那么您将得到True
可迭代对象中的所有项目是否为真。否则,你会得到False
.
在第三个示例中,您传递True
给initializer
ofreduce()
以获得与 a 相同的行为check_all_true()
并避免 a TypeError
。
您还可以使用lambda
函数来解决reduce()
. 这里有些例子:
>>> from functools import reduce
>>> reduce(lambda a, b: bool(a and b), [0, 0, 1, 0, 0])
False
>>> reduce(lambda a, b: bool(a and b), [1, 1, 1, 2, 1])
True
>>> reduce(lambda a, b: bool(a and b), [], True)
True
此lambda
函数与both_true()
返回值非常相似并使用相同的表达式。True
如果两个参数都为真,则返回。否则,它返回False
。
请注意,与 不同check_all_true()
,当您用于reduce()
解决全真用例时,没有短路评估,因为reduce()
在遍历整个可迭代对象之前不会返回。这会给您的代码增加额外的处理时间。
例如,假设您有一个列表lst = [1, 0, 2, 0, 0, 1]
,您需要检查其中的所有项目lst
是否为真。在这种情况下,check_all_true()
将在其循环处理第一对项目(1
和0
)后立即结束,因为它0
为假。您不需要继续迭代,因为您手头的问题已经有了答案。
另一方面,reduce()
解决方案在处理完lst
. 那是五次迭代之后。现在想象一下,如果您正在处理一个大型可迭代对象,这会对您的代码性能产生什么影响!
幸运的是,Python 提供了正确的工具,可以以 Python 风格、可读且高效的方式解决全真问题:内置函数all()
。
您可以使用all(iterable)
来检查中的所有项目iterable
是否为真。以下是all()
工作原理:
>>> all([1, 1, 1, 1, 1])
True
>>> all([1, 1, 1, 0, 1])
False
>>> all([])
True
all()
循环遍历迭代中的项目,检查每个项目的真值。如果all()
发现错误的项目,则返回False
。否则,它返回True
。如果您all()
使用空的可迭代对象调用,那么您会得到,True
因为空的可迭代对象中没有错误的项目。
all()
是针对性能进行了优化的 C 函数。此功能也使用短路评估来实现。因此,如果您正在处理 Python 中的全真问题,那么您应该考虑使用all()
而不是reduce()
.
检查任何值是否为真
Python 的另一个常见用例reduce()
是any-true 用例。这一次,您需要确定迭代中是否至少有一项为真。要解决这个问题,您需要编写一个函数,该函数接受一个可迭代对象,并True
在可迭代对象中的任何项为真时返回,False
否则返回。看看这个函数的以下实现:
>>> def check_any_true(iterable):
... for item in iterable:
... if item:
... return True
... return False
...
>>> check_any_true([0, 0, 0, 1, 0])
True
>>> check_any_true([0, 0, 0, 0, 0])
False
>>> check_any_true([])
False
如果至少有一项iterable
为真,则check_any_true()
返回True
。False
仅当所有项目都为假或可迭代对象为空时才返回。此函数还实现了短路评估,因为它会在找到真值(如果有)后立即返回。
要使用 Python 的 解决此问题reduce()
,您需要编写一个函数,该函数接受两个参数并True
在其中至少一个为真时返回。如果两者都为假,则该函数应返回False
。
这是此功能的可能实现:
>>> def any_true(a, b):
... return bool(a or b)
...
>>> any_true(1, 0)
True
>>> any_true(0, 1)
True
>>> any_true(0, 0)
False
any_true()
返回True
如果其参数的至少一个是真的。如果两个参数都为假,则any_true()
返回False
。与both_true()
上一节一样,any_true()
用于bool()
将表达式的结果转换a or b
为True
或False
。
在Python的or
运营商从工作方式略有不同and
。它返回表达式中的第一个真实对象或最后一个对象。查看以下示例:
>>> a = 1
>>> b = 2
>>> a or b
1
>>> a = 0
>>> b = 1
>>> a or b
1
>>> a = 0
>>> b = []
>>> a or b
[]
Pythonor
运算符返回第一个 true 对象,或者如果两者都为 false,则返回最后一个对象。因此,您还需要使用bool()
从any_true()
.
一旦你有了这个功能,你就可以继续减少。看看以下对 的调用reduce()
:
>>> from functools import reduce
>>> reduce(any_true, [0, 0, 0, 1, 0])
True
>>> reduce(any_true, [0, 0, 0, 0, 0])
False
>>> reduce(any_true, [], False)
False
您已经使用 Python 的reduce()
. 请注意,在第三个示例中,您传递False
给 的初始值设定项reduce()
以重现原始行为check_any_true()
并避免TypeError
.
注意:与上一节中的示例一样,这些示例reduce()
不进行短路评估。这意味着它们会影响代码的性能。
您还可以使用lambda
with 函数reduce()
来解决 any-true 用例。您可以这样做:
>>> from functools import reduce
>>> reduce(lambda a, b: bool(a or b), [0, 0, 1, 1, 0])
True
>>> reduce(lambda a, b: bool(a or b), [0, 0, 0, 0, 0])
False
>>> reduce(lambda a, b: bool(a or b), [], False)
False
此lambda
功能与any_true()
. True
如果它的两个参数中的任何一个为真,它就会返回。如果两个参数都为假,则返回False
。
即使此解决方案只需要一行代码,它仍然会使您的代码不可读或至少难以理解。同样,Python 提供了一个工具,可以在不使用reduce()
的情况下有效地解决任何真问题:内置函数any()
。
any(iterable)
遍历 中的项目iterable
,测试每个项目的真值,直到找到一个真项目。该函数True
在找到真值后立即返回。如果any()
没有找到真值,则返回False
。下面是一个例子:
>>> any([0, 0, 0, 0, 0])
False
>>> any([0, 0, 0, 1, 0])
True
>>> any([])
False
同样,您无需导入any()
即可在代码中使用它。any()
按预期工作。False
如果迭代中的所有项目都为假,则返回。否则,它返回True
。请注意,如果您any()
使用空的可迭代对象调用,那么您会得到,False
因为空的可迭代对象中没有真正的项目。
与 一样all()
,any()
是针对性能进行了优化的 C 函数。它还使用短路评估来实现。因此,如果您正在处理 Python 中的 any-true 问题,请考虑使用any()
代替reduce()
.
比较reduce()
和accumulate()
一个Python函数调用accumulate()
中的生活itertools
和行为方式类似reduce()
。accumulate(iterable[, func])
接受一个必需的参数 ,iterable
它可以是任何 Python 可迭代的。可选的第二个参数func
需要是一个函数(或一个可调用对象),它接受两个参数并返回一个值。
accumulate()
返回一个迭代器。此迭代器中的每一项都将是func
执行计算的累积结果。默认计算是总和。如果您不向 提供函数accumulate()
,则结果迭代器中的每一项都将是前iterable
一项加上手头项的累加和。
查看以下示例:
>>> from itertools import accumulate
>>> from operator import add
>>> from functools import reduce
>>> numbers = [1, 2, 3, 4]
>>> list(accumulate(numbers))
[1, 3, 6, 10]
>>> reduce(add, numbers)
10
请注意,结果迭代器中的最后一个值与reduce()
返回的值相同。这是这两个函数之间的主要相似之处。
注意:由于accumulate()
返回一个迭代器,所以需要调用list()
来消费迭代器并得到一个列表对象作为输出。
如果,另一方面,您提供的两个参数的函数(或可调用)到func
的参数accumulate()
中所产生的,那么项目的迭代器将累加结果的计算由执行func
。这是一个使用的示例operator.mul()
:
>>> from itertools import accumulate
>>> from operator import mul
>>> from functools import reduce
>>> numbers = [1, 2, 3, 4]
>>> list(accumulate(numbers, mul))
[1, 2, 6, 24]
>>> reduce(mul, numbers)
24
在此示例中,您可以再次看到 的返回值中的最后一项accumulate()
等于 返回的值reduce()
。
考虑性能和可读性
Python 的reduce()
性能可能非常糟糕,因为它通过多次调用函数来工作。这会使您的代码变慢且效率低下。使用reduce()
时,用复杂的用户定义的函数或使用但也容易影响你的代码的可读性lambda
功能。
在本教程中,您已经了解到 Python 提供了一堆可以优雅地替换 的工具,reduce()
至少对于它的主要用例是这样。到目前为止,以下是您阅读的主要内容:
-
reduce()
尽可能使用专用函数来解决 Python 的用例。sum()
,all()
,any()
,max()
,min()
,len()
,math.prod()
, 等函数将使您的代码更快、更易读、更易于维护和Pythonic。 -
避免复杂的用户定义的函数使用时
reduce()
。这些类型的函数会使您的代码难以阅读和理解。您可以改用显式且可读的for
循环。 -
lambda
使用时避免复杂的函数reduce()
。它们还可能使您的代码不可读和令人困惑。
第二点和第三点是圭多自己在说以下内容时的担忧:
所以现在
reduce()
。这实际上是我一直最讨厌的一个,因为除了几个涉及+
or 的例子*
,几乎每次我看到一个reduce()
带有非平凡函数参数的调用时,我都需要拿起笔和纸来绘制实际被喂食的内容在我理解应该做什么之前进入该功能reduce()
。所以在我看来, 的适用性reduce()
几乎仅限于关联运算符,在所有其他情况下,最好明确写出累加循环。(来源)
接下来的两节将帮助您在代码中实现这一一般建议。他们还提供了一些额外的建议,可以帮助您reduce()
在真正需要使用 Python 时有效地使用它。
性能是关键
如果您打算使用reduce()
来解决本教程中介绍的用例,那么与使用专用内置函数的代码相比,您的代码会慢得多。在以下示例中,您将使用timeit.timeit()
来快速测量一小段 Python 代码的执行时间并了解它们的总体性能。
timeit()
需要几个参数,但对于这些示例,您只需要使用以下参数:
stmt
持有声明,你需要时间。setup
为一般设置采用附加语句,例如import
statements。globals
保存一个包含全局命名空间的字典,你需要使用它来运行stmt
.
看看下面的例子,时间总和用例使用reduce()
不同的工具并使用 Pythonsum()
进行比较:
>>> from functools import reduce
>>> from timeit import timeit
>>> # Using a user-defined function
>>> def add(a, b):
... return a + b
...
>>> use_add = "functools.reduce(add, range(100))"
>>> timeit(use_add, "import functools", globals={"add": add})
13.443158069014316
>>> # Using a lambda expression
>>> use_lambda = "functools.reduce(lambda x, y: x + y, range(100))"
>>> timeit(use_lambda, "import functools")
11.998800784000196
>>> # Using operator.add()
>>> use_operator_add = "functools.reduce(operator.add, range(100))"
>>> timeit(use_operator_add, "import functools, operator")
5.183870767941698
>>> # Using sum()
>>> timeit("sum(range(100))", globals={"sum": sum})
1.1643308430211619
即使根据您的硬件您会得到不同的数字,您也可能会使用sum()
. 这个内置函数也是求和问题最具可读性和 Pythonic 的解决方案。
注意:有关如何为代码计时的更多详细方法,请查看Python 计时器函数:监视代码的三种方法。
您的第二个最佳选择是reduce()
与operator.add()
. 中的函数operator
是用 C 编写的,并针对性能进行了高度优化。因此,它们的性能应该比用户定义的函数、lambda
函数或for
循环更好。
可读性计数
在使用 Python 的reduce()
. 尽管reduce()
通常会比 Pythonfor
循环执行得更好,正如 Guido 自己所说的那样,但干净和Pythonic 的循环通常比使用reduce()
.
The What's New In Python 3.0 guide强调了这个想法,它说以下内容:
functools.reduce()
如果您确实需要,请使用它;然而,在 99% 的情况下,显式for
循环更具可读性。(来源)
为了更好地理解可读性的重要性,假设您开始学习 Python,并且您正在尝试解决一个关于计算可迭代对象中所有偶数之和的练习。
如果您已经了解 Pythonreduce()
并且过去做过一些函数式编程,那么您可能会想出以下解决方案:
>>> from functools import reduce
>>> def sum_even(it):
... return reduce(lambda x, y: x + y if not y % 2 else x, it, 0)
...
>>> sum_even([1, 2, 3, 4])
6
在此函数中,您用于reduce()
对可迭代对象中的偶数进行累积求和。该lambda
函数接受两个参数x
和y
,如果它们是偶数,则返回它们的总和。否则,它返回x
,它保存前一个总和的结果。
此外,您设置initializer
为 ,0
因为否则您的总和将具有1
( 中的第一个值iterable
)的初始值,该值不是偶数并且会在您的函数中引入错误。
该函数按您的预期工作,您对结果感到满意。但是,你继续挖成Python,了解sum()
和生成器表达式。您决定使用这些新工具重新设计您的函数,您的函数现在如下所示:
>>> def sum_even(iterable):
... return sum(num for num in iterable if not num % 2)
...
>>> sum_even([1, 2, 3, 4])
6
当您看到这段代码时,您感到非常自豪,而且您应该这样做。你做得很好!这是一个漂亮的 Python 函数,几乎读起来像简单的英语。它也是高效和 Pythonic 的。你怎么认为?
结论
Pythonreduce()
允许您使用 Python 可调用对象和lambda
函数对可迭代对象执行归约操作。reduce()
将函数应用于迭代中的项目并将它们减少到单个累积值。
在本教程中,您学习了:
- 什么是缩减或折叠,以及何时可能有用
- 如何使用 Python
reduce()
解决常见的归约问题,例如数字求和或乘法 - 您可以使用哪些Pythonic 工具来有效地替换
reduce()
您的代码
有了这些知识,在解决 Python 中的归约问题时,您将能够决定哪些工具最适合您的编码需求。
多年来,reduce()
已被更多 Pythonic 工具所取代,例如sum()
、min()
、max()
all()
、any()
等。然而,reduce()
仍然存在并且仍然在函数式程序员中流行。如果您对使用reduce()
Python 或其任何替代方案有任何疑问或想法,请务必在下面的评论中发布它们。
- 点赞
- 收藏
- 关注作者
评论(0)