Map, Filter, Reduce - 在Python中处理数据流


Map, Filter, Reduce - 在Python中处理数据流

你知道如何像Java流一样使用Python流吗?

一个流是一个元素序列。通过map()、filter()和reduce()–函数式编程的三个基石函数–你可以对一个元素序列进行操作。在这篇文章中,我们将学习如何在 Python 中处理流,就像我们在 Java 中处理流一样。

但是首先,让我们说一说函数式编程。

什么是函数式编程?

函数式编程是一种编程范式,它将问题分解为各个函数。如果可能的话,每个函数都接受一组输入参数并产生一个输出。

在这种范式中,我们尽可能避免易变的数据类型和状态变化。

它还强调递归而不是循环,专注于列表、纯函数和高阶函数。

在这篇文章中,我们将探讨 Python 中的 map()、 filter() 和 reduce() 。

首先,让我们注意到 map()、filter()和 reduce() 是用 C 语言编写的,并且在速度和内存使用方面高度优化,使它们成为比常规 Python for 循环更好的选择。

作为先决条件,掌握一些 Python 中的函数知识是非常必要的。如果你需要复习,请参考《如何在Python中定义一个函数》一文。

在Python中处理流:  map()

map()接收一个函数和一个或多个迭代变量作为参数。输出是一个迭代器,返回转换后的项目。

语法如下:

map(function, iterable[, iterable1, iterable2,..., iterableN])

map()的第一个参数是一个转换函数,其中每个原始项目被转换为一个新项目。它可以是任何 Python 可调用函数。

假设你需要取一个数值列表,并将其转换为一个包含原始列表中每个数字的立方值的列表。你可以使用for循环,代码如下:

>>> # 定义要转换的数字和一个空的立方体列表

>> num = [2, 3, 6, 9, 10]

>> cube = []

>> # 定义for循环来转换数字

>> for num:

 ... cube.append(n ** 3)

>> # 计算num的立方体

>> cube[8, 27, 216, 729, 1000]

这个循环返回一个立方体值的列表。for循环对num进行迭代,并对每个值进行立方体转换。最后,它将结果值存储在cube中。

map()可以在没有for循环的情况下实现同样的结果:

>>> # 定义转换函数

>> def cube(num):

 ....   return num ** 3

>> # 要转换的数字列表

>> num = [2, 3, 6, 9, 10]

>> # 调用map函数对每个数字进行cube处理

>> cubed = map(cube, num)

>> # 创建一个包含cube值的列表

>> list(cube) [8, 27, 216, 729, 1000]

上述例子说明了如何用map()和一个用户定义的函数来转换一个值列表。

任何类型的 Python 可调用程序都可以使用 map() ,例如类、实例方法、类方法、静态方法和函数。

使用 map() 时的一个典型模式是使用一个 Python lambda 函数作为第一个参数。Lambda 函数是一种方便的方式,可以将基于表达式的函数传递给 map() 。为了说明这一点,我们可以重新使用使用Python lambda函数的立方体值的例子:

>>> # 要转换的输入数字列表

>> num = [2, 3, 6, 9, 10]

>> # 定义一个lambda函数来遍历num的每个值。

>> cubed = map(lambda n: n ** 3, num)

>> # 创建一个包含立方值的列表

>> list(cubed)[8, 27, 216, 729, 1000]

如果你向map()输入多个迭代器,那么转换函数必须接受与你传入的迭代器数量相同的参数。

当传递多个迭代表时,map()将对迭代表中的元素进行分组。

这种技术对于合并两个或多个使用不同数学运算的数值迭代表是非常有用的。下面是一些使用 Python lambda 函数在几个输入迭代表上计算各种数学运算的例子:

>>> list(map(lambda x, y: x / y, [6, 3, 5], [2, 4, 6])) [3.0, 0. 75, 0.833333333334]

>> list(map(lambda x, y, z: x * y + z, [6, 2], [7, 3], [8, 10])[50, 16]

在第一个例子中,我们使用除法运算来合并两个各有三个项目的迭代表。在第二个例子中,我们将三个迭代项的值相乘并相加,即6 x 7 + 8 = 50和2 x 3 + 10 = 16。

另外,map()有助于处理和转换数值迭代项;很多与数学相关的转换都可以用map()来完成。

我们还应该提到starmap(),它与map()非常相似。根据 Python 文档,当参数已经被分组为来自单个迭代器的图元时,starmap() 被用来代替 map(),这意味着数据已经被 “预压缩”。

为了调用 starmap() ,我们需要导入 itertools。让我们运行一个快速的例子:

>>> import itertools

>> # 定义一个图元列表

>> num = [(2, 3), (6, 9), (10,12)]

>> # 对图元列表定义一个lambda函数

>> multiply = itertools. starmap(lambda x,y: x * y, num)

>> # 创建一个包含倍增值的列表

>> list(multiply)[6, 54, 120]在Python中处理流:  filter()

一个过滤操作处理一个迭代器,提取满足给定操作的项目。它可以用 Python’filter() 内置函数来执行。

基本语法是:

filter(function, iterable)

过滤函数可以过滤掉不需要的值,在输出中保留需要的值。函数参数必须是一个单参数的函数。它通常是一个布尔值函数,返回 True 或 False。

iterable 参数可以是任何 Python 迭代器,例如一个 list、一个 tuple 或一个 set。它也可以持有生成器和迭代器对象。注意 filter() 只接受一个 iterable。

filter() 经常与 Python lambda 函数一起使用,作为定义用户定义的函数的另一种方式。让我们运行一个例子,我们想从一个列表中只得到偶数:

>>> # 数字列表

>> num = [12, 37, 34, 26, 9, 250, 451, 3, 10]

>> # 定义 lambda 函数来过滤偶数

>> even = list(filter(lambda x: (x % 2 == 0), num))

>> # 打印偶数

>> print(even) [12, 34, 26, 250, 10]

上面的例子使用 filter() 来检查数字是否是偶数。如果满足这个条件并返回True,那么偶数就 "通过了过滤器"。

注意,可以用列表理解代替 filter() :

# 用 filter() 生成一个列表,list(filter(function, iterable))# 用列表理解生成一个列表[i for i in iterable if function(i)]

在这两种情况下,目的是返回一个列表对象。

在 Python 中操作列表时,列表理解的方法比 filter() 更明确。然而,列表理解缺乏懒惰的评估。而且,通过阅读代码,我们立即知道 filter() 执行了一个过滤操作。

在 Python 中使用 groupby() 和 sort()

在这一部分,我们将讨论在 Python 中处理流的其它工具:sort() 和 groupby()

sort() 方法是一个在 Python 中操作列表的有用工具。例如,如果你需要对一个列表进行升序或倒序排序,你可以使用下面的方法:

>>> num = [24, 4, 13, 35, 28]

>> # 对列表进行升序排序

>> num.sort()

>> print(num)[4, 13, 24, 28, 35]

而对降序排序:

>>>> # 对列表进行降序排序

>> numbers. sort(reverse=True)

>> print(numbers)[35, 28, 24, 13, 4]

需要注意的是,sort()方法会改变原始列表,因此不可能将列表中的项目恢复到原来的位置。该键对于指定对每个单独的可迭代项采取什么行动很有用。返回值将类似于一个字典,因为它是以{key:value}的形式存在的。正因为如此,用与用于分组的键相同的键对项目进行分类是非常重要的。

让我们运行一个例子,在这个例子中,我们将一些月度支出存储为一个图元列表。

我们希望将这些支出按月分组,并最终计算出每月的总支出。

>>> import itertools

>> # 创建一个每月支出的图元列表

>> spendings = [("一月", 25), ("二月", 47), ("三月", 38), ("三月", 54), ("四月", 67), ("一月", 56), ("二月", 32), ("五月", 78), ("一月", 54), ("四月", 45)]

>> # 创建一个空字典来存储数据

>> spendings_dic = {}> # 定义一个func变量来指定分组的关键

>> func = lambda x: x[0]

>> # 在字典中按月对每月的花费进行分组

>> for key, group in groupby(sorted(spendings, key=func), func):

. ... spendings_dic[key] = list(group)

>>> spendings_dic{'四月': [('四月', 67), ('四月', 45)], '二月': [('二月', 47), ('二月', 32)], '一月': [('一月', 25), ('一月', 56), ('一月', 54)], '三月': [('三月', 38), ('三月', 54)], '五月': [('May', 78)]}

在上面的片段中,我们用sorted()而不是sort()。

与 sort() 相反,sorted() 将创建一个原始列表的副本,使我们有可能检索到原始顺序。因为 sorted() 需要创建一个原始列表的副本,所以它比 sort() 要慢。如果你想了解更多关于 Python 中的排序,我写了一篇文章,解释了定义你自己的排序标准的不同方法。

最后,我们可以使用上一节中的map()来对每月的支出进行求和:

>>> # 应用map()来对每月的支出进行求和

>> monthly_spendings = {key: sum(map(lambda x: x[1], value)) for key, value in spendings_dic.items()}

>> monthly_spendings{'四月': 112, '二月': 79, '一月': 135, '三月': 92, '五月': 78}

要学习应用Python lambda表达式、过滤行以及用Pandas在Python数据框中选择列,请参阅Yigit Aras’关于在数据框中过滤行和选择列的优秀文章。

在Python中处理流:  reduce()

reduce()函数实现了一种叫做折叠或减少的技术。它采用了一个现有的函数,将其累积应用于 iterable 中的所有项目,并返回一个单一的最终值。

reduce() 最初是一个内置函数,应该被删除。由于一些可能的性能和可读性问题,它在 Python 3.0 中被移到 functools.reduce() 中。

除非你不能找到 reduce() 以外的任何解决方案,否则你应该避免使用它。reduce() 函数会产生一些糟糕的性能问题,因为它多次调用函数,使你的代码变得缓慢和低效。

只要有可能,就用一个专门的函数来解决这些用例。诸如sum()、any()、all()、min()、max()、len()、math.prod()等函数更快、更易读,而且是Pythonic。这些函数也是高度优化的,并且是用C语言实现的,因此速度快、效率高。

当你将reduce()与复杂的用户定义函数或lambda函数一起使用时,也会影响到代码的可读性。reduce()通常会比Python for loop表现更好,但正如Python创造者Guido Van Rossum解释的那样,Python loop通常比reduce()更容易理解。

为了完整地解释函数式编程中使用的三种主要方法,我将简要地解释 reduce() 以及一些使用案例。

reduce() 的语法如下:

functools.reduce(function, iterable[, initializer])

Python 文档将 reduce() 的第一个参数称为 “a function of two arguments” 。然而,只要有两个参数,我们可以传递任何 Python 可调用对象。可调用对象包括类、实例方法、类方法、静态方法和函数。

第二个必要的参数,iterable,可以是任何 Python 的可迭代对象。官方的 Python 词汇表将迭代对象定义为 “能够一次返回其成员的对象"。迭代对象的例子包括所有的序列类型 (如 list、str 和 tuple),以及一些非序列类型,如 dict、文件对象,以及你用 __iter__() 方法或用实现序列语义的 __getitem__() 方法定义的任何类的对象。如果你为初始化器提供了一个值,那么 reduce() 将把它送入其第一个参数的第一次函数调用中。

如果你想用 reduce() 来处理可能是空的迭代表,那么给初始化器提供一个值是一个好的做法。这个值将被用作迭代器为空时的默认返回值。如果您不提供任何值,reduce() 将引发一个 TypeError。

让我们运行一些示例。与上一节一样,我们可以使用 reduce() 来计算每年的开支:

>>> from functools import reduce

>> yearly_spendings = reduce(lambda x, y:x + y, monthly_spendings.values())

>> print(yearly_spendings)496

下面的例子更难,但它们是有用的 reduce() 用例。

我们想把一个 [[1, 3, 5], [7, 9], [11, 13, 15]] 的列表变成 [1, 3, 5, 7, 9, 11, 13, 15]。

我们可以这样做:

>>> from functools import reduce

>> reduce(list.__add__, [[1, 3, 5], [7, 9], [11, 13, 15]], []) [1, 3, 5, 7, 9, 11, 13, 15]

我们也可以使用 reduce() 来寻找 n 个列表的交点。例如:

>>> from functools import reduce

>> num = [[5, 7, 8, 10, 3], [5, 12, 45, 8, 9], [8, 39, 90, 5, 12]]

>> res = reduce(set.intersection, map(set, num))

>> print(res){8, 5}

输出是一个集。你可以在这里找到更多关于 Python 中的集合的信息。

尽管有上面提到的例子,reduce() 的使用案例数量很少,这也解释了为什么它在 Python 3 中被从内置函数中删除。大多数情况下,你最好使用另一种方法来处理 Python 中的列表。

关于Python流的结束语

在这篇文章中,你了解了Python中的函数式编程和它的三个主要方法,map()、filter()和reduce()。你可以用它们来操作 Python 中的列表。我们还讨论了如何使用 groupby() 和 sort()。

所有这些方法使在 Python 中处理流变得更加容易。我鼓励你使用它们,探索它们的作用,并比较其结果。你还可以在LearnPython.com上发现更多的资源,以了解更多关于Python的一般知识。


本文标签

热门标签

会员评论