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

流是元素序列。使用 map() , filter() 、 和 reduce() (函数式编程的三个基石函数),您可以对元素序列进行操作。在本文中,我们将学习如何在 Python 中使用流,就像在 Java 中使用流一样。

但首先,让我们谈谈函数式编程。

什么是函数式编程?

函数式编程是一种将问题分解为单个函数的编程范式。如果可能的话,每个函数都会接受一组输入参数并产生输出。在这个范式中,我们尽可能避免可变数据类型和状态更改。

它还强调 递归 而不是循环,重点关注列表、 纯函数 和 高阶函数 .

在本文中,我们将探索 map() , filter() 、和 reduce() 。这些是用于执行 函数式编程中的基础映射、过滤和归约操作 , 的 Python 方法 。

首先,我们要注意 map() , filter() ,和 reduce() 是用 C 编写的,并且在速度和内存使用方面经过高度优化,因此它们是比常规 Python for 循环更好的选择。

作为先决条件,必须对 Python 中的函数有所了解。如果您需要复习,请参阅文章 如何在 Python 中定义函数 .

在 Python 中使用流:map()

map() 以函数和一个或多个可迭代对象作为参数。输出是一个返回转换后项的迭代器。

语法如下:

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

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

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

>>> # Define numbers to transform and an empty cube list
>>> num = [2, 3, 6, 9, 10]
>>> cube = []

>>> # Define for loop to transform the numbers
>>> for n in num:
...     cube.append(n ** 3)

>>> # Compute cube of num
>>> cube
[8, 27, 216, 729, 1000]

此循环返回立方体值列表。 for 循环遍历 num 并对每个值应用立方体转换。最后,它将结果值存储在 cube .

map() 无需 for 循环即可达到相同的结果:

>>> # Define the transformation function
>>> def cube(num):
...   return num ** 3

>>> # List of numbers to transform
>>> num = [2, 3, 6, 9, 10]

>>> # Call map function to apply cube on each number
>>> cubed = map(cube, num)

>>> # Create a list containing the cubed values
>>> list(cubed)
[8, 27, 216, 729, 1000]

用户定义的函数 map() 转换值列表

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

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

>>> # List of input numbers to transform
>>> num = [2, 3, 6, 9, 10]

>>> # Define a lambda function to iterate on each value of num.
>>> cubed = map(lambda n: n ** 3, num)

>>> # Create a list containing the cubed values
>>> 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.8333333333333334]

>>> 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

>>> # Define a list of tuples
>>> num = [(2, 3), (6, 9), (10,12)]

>>> # Define a lambda function to a list of tuples
>>> multiply = itertools.starmap(lambda x,y: x * y, num)

>>> # Create a list containing the multiplied values
>>> list(multiply)
[6, 54, 120]

使用 Python 中的流:filter()

过滤操作处理可迭代对象并提取满足给定操作的项目。可以使用 Python 的 filter() 内置函数执行此操作。

基本语法是:

filter(function, iterable)

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

参数 iterable 可以是任何 Python 可迭代对象,例如列表、元组或集合。它还可以包含生成器和迭代器对象。请注意, filter() 只接受一个可迭代对象。

filter() 通常与 Python lambda 函数一起使用,作为定义用户定义函数的替代方法。让我们运行一个示例,其中我们只想从列表中获取偶数:

>>> # List of numbers
>>> num = [12, 37, 34, 26, 9, 250, 451, 3, 10]
  
>>> # Define lambda function to filter even numbers
>>> even = list(filter(lambda x: (x % 2 == 0), num)) 
  
>>> # Print the even numbers
>>> print(even) 
[12, 34, 26, 250, 10]

上面的例子用来 filter() 检查数字是否为偶数。如果满足此条件并返回 True,则偶数“通过过滤器”。

请注意,可以 filter() 用列表推导来替换:

# Generate a list with filter()
list(filter(function, iterable))

# Generate a list with a list comprehension
[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]

>>> # sort the list in ascending order
>>> num.sort()
>>> print(num)
[4, 13, 24, 28, 35]

按降序排列:

>>> # sort the list in descending order
>>> numbers.sort(reverse=True)
>>> print(numbers)
[35, 28, 24, 13, 4]

值得注意的是,该 sort() 方法会改变原始列表,因此不可能将列表的项目恢复到其原始位置。

接下来, itertools.groupby() 获取可迭代对象列表并根据指定的键对它们进行分组。键可用于指定对每个对象采取什么操作 iterable 。返回值将类似于字典,因为它是 { key:value } 形式。因此, items 使用与分组相同的键对对象进行排序非常重要。这将确保代码的一致性并避免意外结果。

让我们运行一个示例,其中我们将一些每月支出存储为元组列表。

我们希望按月对这些费用进行分组,最后计算出每月的总费用。

>>> import itertools

>>> # Create a list of monthly spendings as a list of tuples  
>>> spendings = [("January", 25), ("February", 47), ("March", 38), ("March", 54), ("April", 67), 
             ("January", 56), ("February", 32), ("May", 78), ("January", 54), ("April", 45)]

>>> # Create an empty dictionary to store the data
>>> spendings_dic = {}

>>> # Define a func variable to specify the grouping key
>>> func = lambda x: x[0]

>>> # Group monthly spendings by month in a dictionary 
>>> for key, group in groupby(sorted(spendings, key=func), func):
...     spendings_dic[key] = list(group) 

>>> spendings_dic
{'April': [('April', 67), ('April', 45)],
 'February': [('February', 47), ('February', 32)],
 'January': [('January', 25), ('January', 56), ('January', 54)],
 'March': [('March', 38), ('March', 54)],
 'May': [('May', 78)]}

在上面的代码片段中,我们使用了 sorted() 而不是 sort() 。这是因为我们想要对还不是列表的可迭代对象进行排序。

相反, sort() , sorted() 它会创建原始列表的副本,从而可以检索原始顺序。由于 sorted() 需要创建原始列表的副本,因此它比 慢 sort() 定义自己的排序标准 defining your own sorting criteria .

最后,我们可以使用 map() 上一节的内容来总结每月的支出:

>>> # Apply map() to sum the monthly spendings
>>> monthly_spendings = {key: sum(map(lambda x: x[1], value)) for key, value in spendings_dic.items()}
>>> monthly_spendings
{'April': 112, 'February': 79, 'January': 135, 'March': 92, 'May': 78}

要了解如何使用 Pandas 应用 Python lambda 表达式、过滤行以及选择 Python 数据框中的列,请参阅 Yigit Aras 关于 过滤行和选择数据框中的列 .

使用 Python 中的流:reduce()

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

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

除非您找不到除 之外的其他解决方案 reduce() ,否则应避免使用它。该 reduce() 函数可能会造成一些严重的性能问题,因为它会多次调用函数,从而使您的代码变得缓慢且效率低下。

尽可能使用专用函数来解决这些用例。诸如这样的函数 sum() , any() , all() , min() , max() , len() , math.prod() 速度更快、更易读且更符合 Python 风格。这些函数还经过高度优化,并用 C 语言实现,因此速度更快、效率更高。

reduce() 当您将其与复杂的用户定义函数或 lambda 函数一起使用时,也会损害代码的可读性。reduce() 通常比 Python for 循环表现更好,但正如 Python 创建者 Guido Van Rossum 所解释的那样,Python 循环通常比 更容易理解 reduce() 。他建议将 的适用性 reduce() 限制在结合运算符上。

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

reduce() 语法如下:

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

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

第二个必需参数 iterable 可以是任何 Python 可迭代对象。官方 Python 词汇表 将可迭代对象定义为“能够一次返回一个成员的对象。可迭代对象的示例包括所有序列类型(例如列表、字符串和元组)和一些非序列类型,如字典、文件对象以及使用 __iter__() 方法或实现序列语义的 __getitem__() 方法定义的任何类的对象。”

的参数 initializer reduce() 可选的。如果您向初始化程序提供了一个值, reduce() 则将把它提供给函数的第一个参数的第一次调用。否则,它将使用可迭代对象中的第一个值。

如果您想要使用 reduce() 来处理可能为空的可迭代对象,那么为初始化程序提供一个值是一种很好的做法。当 为空时,此值将用作默认返回值 iterable 。如果您不提供任何值, 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}

输出是一个集合。你可以 在这里 .

尽管有上述示例,但用例的数量 reduce() 却很少,这解释了为什么它已从 Python 3 中的内置函数中删除。大多数时候,最好使用另一种方法来操作 Python 中的列表。

关于 Python 流的总结

在本文中,您了解了 Python 中的函数式编程及其三种主要方法、 map() , filter() reduce() 。您可以使用它们来操作 Python 中的列表。我们还讨论了如何使用 groupby() sort() .

所有这些方法都使在 Python 中处理流变得更加容易。我鼓励您尝试一下,探索它们的作用,并比较结果。您还可以在 ,以了解有关 Python 的更多信息。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部