本文将学习如何将 map() 与其他函数工具结合起来,并进行更复杂的转换;并学习可以用什么工具来替代map(),使代码更加Pythonic。
map() 与其他函数结合使用
现在我们已经介绍了如何使用 map() 来完成涉及迭代表的不同任务。然而,如果将map()与其他函数式工具(如filter() 和 reduce())一起使用,那么我们可以对迭代变量进行更复杂的转换。这就是在下面要介绍的内容。
map() 和 filter()
有时需要处理一个输入迭代器,并返回另一个迭代器,这个迭代器是过滤掉输入迭代器中不需要的值而得到的。在这种情况下,Python 的 filter() 可以是一个很好的选择。filter() 是一个内置的函数,需要两个位置参数。
- function 函数将是一个谓词或布尔值函数,一个根据输入数据返回真或假的函数。
- iterable 是任何Python可迭代对象。
filter() 产生的是函数返回 True 的输入迭代器的项目。如果把 None传给函数,那么 filter() 使用身份函数。这意味着 filter() 将检查可迭代对象中每个项目的真值,并过滤掉所有虚假的项目。
为了说明如何使用 map() 和 filter(),假设我们需要计算一个列表中所有数值的平方根,如果列表中包含负值时,会得到一个错误。
如果有一个负数作为参数,math.sqrt()会引发一个ValueError。为了避免这个问题,可以使用filter()来过滤掉所有的负值,然后找到剩余正值的平方根。看下面的例子。
is_positive() 是一个谓词函数,它接受一个数字作为参数,如果该数字大于或等于0,则返回True。可以将 is_positive() 传递给 filter(),以去除数字中的所有负数。因此对 map() 的调用将只处理正数,从而确保 math.sqrt() 不会抛出一个ValueError。
map() 和 reduce()
reduce() 是一个函数,它存在于 Python 标准库中一个叫做 functools 的模块中。reduce() 是 Python 的另一个核心函数工具,当我们需要将一个函数应用于一个迭代器并将其减少到一个单一的累积值时,它非常有用,这种操作通常被称为减少或折叠。reduce() 需要两个必要的参数。
- function 函数可以是任何接受两个参数并返回一个值的 Python 可调用函数。
- iterable 可以是任何 Python 的可迭代对象。
reduce() 将对可迭代对象 iterable 中的所有项目应用函数,并累积计算出一个最终值。
下面的例子结合了 map() 和 reduce() 来计算主目录中所有文件的总大小:
在这个例子中,我们调用os.path.expanduser("~")来获得主目录的路径。然后在该路径上调用 os.listdir() ,得到一个包含所有文件路径的列表。
对map()的调用使用 os.path.getsize() 来获得每个文件的大小。最后使用 reduce() 和operator.add() 来获得每个文件大小的累积总和。最后的结果是主目录中所有文件的总大小,单位是字节。
注意:几年前,谷歌开发并开始使用他们称之为MapReduce的编程模型。这是一种新的数据处理方式,旨在使用集群上的并行和分布式计算来管理大数据。
这个模型的灵感来自于函数式编程中常用的map和reduce操作的结合。
MapReduce模型对谷歌在合理时间内处理海量数据的能力产生了巨大影响。然而,到了2014年,谷歌不再使用MapReduce作为他们的主要处理模式。
现在,我们可以找到一些MapReduce的替代实现,如 Apache Hadoop,它是一个使用MapReduce模型的开源软件工具的集合。
尽管可以使用 reduce() 来解决本节所涉及的问题,但 Python 提供了其他的工具,这些工具可以导致一个更加 Pythonic 和高效的解决方案。例如可以使用内置的函数 sum() 来计算主目录中的文件的总大小。
这个例子比我们之前看到的例子可读性和效率都要高很多。
用 starmap() 处理基于元组的可迭代对象
Python的 itertools.starmap() 生成一个迭代器,该迭代器将函数应用于从元组可迭代对象获得的参数,并产生结果。当处理已经分组在元组中的可迭代对象时,它很有用。
map() 和 starmap() 之间的主要区别在于后者使用解包操作符( * )调用其转换函数,将每个元组参数解包为几个位置参数。因此,转换函数被称为 function(*args) 而不是function(arg1, arg2,... argN)。
starmap()的官方文档[1]说,该函数大致等同于下面的Python函数。
这个函数中的for循环对iterable中的项目进行迭代,并产生转换后的项目作为结果。对function(*args)的调用使用了解包操作符,将图元解包为几个位置参数。下面是一些关于starmap()如何工作的例子。
这个函数中的for 循环遍历iterable中的元素,并得到转换后的结果。调用function(*args)使用解包操作符将元组解包为几个位置参数。下面是一些关于starmap()如何工作的例子:
在第一个例子中,使用pow()来计算每个元组中第一个值对第二个值的升幂。这些元组的形式是(基数, 指数)。
如果可迭代对象中的每个元组都有两个元素,那么 function也必须接受两个参数。如果元组有三个元素,那么 function必须接受三个参数,依此类推。否则会得到一个TypeError
如果使用 map() 而不是 starmap(),那么会得到一个不同的结果,因为 map() 从每个元组中抽取一个项目。
注意,map() 需要两个元组,而不是一个元组的列表。map() 在每次迭代中也从每个元组中获取一个值。要使 map() 返回与 starmap() 相同的结果,需要交换值。
在这种情况下,我们有两个元组而不是一个元组的列表,还交换了7和4。现在,第一个元组提供基数,第二个元组提供指数。
用Pythonic风格编码取代map()
像 map()、filter() 和 reduce() 这样的函数式编程工具已经存在很长时间了。然而,列表推导式和生成器表达式几乎在每个用例中都成为了它们的自然替代品。
例如,map() 提供的功能几乎总是用一个列表推导式或生成器表达式来表达更好。在下面两节中,我们将学习如何用列表推导式或生成器表达式来替换对map()的调用,使我们的代码更有可读性和Pythonic。
使用列表推导式
有一个一般的模式,我们可以用一个列表推导式来代替对map()的调用。具体方法如下。
注意,列表推导几乎总是比调用map()读起来更清楚。由于列表推导式在Python开发人员中非常流行,所以在任何地方都可以找到它们。因此,用列表推导式替换 map() 调用会让其他Python开发人员更熟悉你的代码。
这里有一个例子,说明如何用一个列表推导式来代替map(),建立一个平方数的列表。
如果我们比较这两种解决方案,那么我们可能会说,使用列表理解的那个方案更有可读性,因为它读起来几乎像纯英语。另外,列表理解避免了在map()上明确调用list()来建立最终的列表。
使用生成器表达式
map()返回一个map对象,它是一个迭代器,可以按需产生项目。因此,map()的自然替代物是一个生成器表达式[2],因为生成器表达式返回生成器对象,而生成器对象也是按需产生项目的迭代器。
map()返回一个map对象,这是一个按需生成项目的迭代器。因此对map()的自然替换是一个生成器表达式,因为生成器表达式返回生成器对象,这些对象也是生成按需项的迭代器。
众所周知,Python迭代器在内存消耗方面是非常高效的。这就是为什么map()现在返回一个迭代器而不是一个列表的原因。
列表推导式和生成器表达式之间有一个微小的语法差异。第一种方法使用一对方括号('[]')来分隔表达式。第二个使用一对圆括号('()')。因此,要将列表推导式转换为生成器表达式,只需将方括号替换为圆括号。
可以使用生成器表达式来编写代码,它比使用map()的代码读起来更清晰。请看下面的例子。
这段代码与上一节的代码有一个主要区别:把方括号改为一对小括号,把列表理解变成生成器表达式。
生成器表达式通常用作函数调用中的参数。在这种情况下,不需要使用圆括号来创建生成器表达式,因为用于调用函数的圆括号还提供了构建生成器的语法。有了这个想法,通过像这样调用 list(),你可以得到与上面例子相同的结果:
如果在函数调用中使用生成器表达式作为参数,那么就不需要额外的一对小括号。我们用来调用函数的小括号提供了构建生成器的语法。
在内存消耗方面,生成器表达式与 map() 一样高效,因为它们都返回按需生成项的迭代器。然而,生成器表达式几乎总是会提高代码的可读性。它们还使您的代码在其他Python开发人员眼中更像Python。
总结
我们可以使用Python的 map() 对可迭代对象执行映射操作。映射操作包括对可迭代对象中的元素应用转换函数来生成转换后的可迭代对象。通常,map()可以处理和转换可迭代对象,而无需使用显式循环。
在本文中,我们已经学习了map()如何工作以及如何使用它来处理可迭代对象。还了解了一些可以在代码中替换map()的python工具。
至此我们现在知道如何:
- 使用Python的map()
- 使用map()来处理和转换可迭代对象,而不使用显式循环
- 将map() 与 filter() 和 reduce()等函数组合在一起,以执行复杂的转换
- 用列表推导式和生成器表达式等工具替换 map()
有了这些新知识,将能够在代码中使用map(),并以函数式编程风格处理代码。通过将map()替换为列表推导式或生成器表达式,还可以切换到更python化和更现代的风格。
参考资料
[1]starmap的官方文档: https://docs.python.org/3/library/itertools.html#itertools.starmap
[2]生成器表达式: https://realpython.com/introduction-to-python-generators/#building-generators-with-generator-expressions