当前位置: 首页 > 科技观察

Map函数的队友和对手

时间:2023-03-17 17:35:55 科技观察

本文将学习如何将map()与其他函数工具结合,进行更复杂的变换;并了解可以使用哪些工具代替map()来使代码更符合Pythonic。将map()与其他函数一起使用现在我们已经介绍了如何使用map()来完成涉及遍历表的不同任务。但是,如果map()与filter()、reduce()等其他功能工具一起使用,则可以对迭代变量进行更复杂的变换。这就是下面介绍的内容。map()和filter()有时需要处理一个输入迭代器,并返回另一个通过过滤掉输入迭代器中不需要的值而得到的迭代器。在这种情况下,Python的filter()会是一个不错的选择。filter()是一个带有两个位置参数的内置函数。函数函数将是一个谓词或布尔函数,一个根据输入数据返回true或false的函数。iterable是任何Python可迭代对象。filter()生成函数返回True的输入迭代器的项目。如果None传递给函数,则filter()使用恒等函数。这意味着filter()将检查iterable中每个项目的真值并过滤掉任何错误的值。为了说明如何使用map()和filter(),假设我们需要计算列表中所有值的平方根,如果列表包含负值,则会出错。>>>importmath>>>math.sqrt(-16)Traceback(最近调用最后):文件“”,第1行,在math.sqrt(-16)ValueError:数学域错误如果使用负数作为参数,math.sqrt()引发ValueError。为了避免这个问题,使用filter()过滤掉所有的负值,然后求剩余正值的平方根。请参见下面的示例。>>>导入数学>>>defis_positive(num):...returnnum>=0...>>>defsanitized_sqrt(numbers):...cleaned_iter=map(math.sqrt,filter(is_positive,numbers)))...returnlist(cleaned_iter)...>>>sanitized_sqrt([25,9,81,-16,0])[5.0,3.0,9.0,0.0]is_positive()是一个谓词函数,它接受一个数字作为参数,如果该数字大于或等于0,则返回True。可以将is_positive()传递给filter()以从数字中删除所有负数。因此,对map()的调用只会处理正数,从而确保math.sqrt()不会抛出ValueError。map()和reduce()reduce()是一个函数,存在于Python标准库中名为functools的模块中。reduce()是Python的另一个核心功能工具。当我们需要将函数应用于迭代器并将其减少为单个累加值时,它非常有用。此操作通常称为缩减或折叠。reduce()需要两个必需的参数。函数可以是任何接受两个参数并返回一个值的Python可调用函数。iterable可以是任何Python可迭代对象。reduce()会将函数应用于可迭代对象中的所有项目并累积计算最终值。以下示例结合map()和reduce()来计算主目录中所有文件的总大小:>>>importfunctools>>>importoperator>>>importos>>>importos.path>>>files=os.listdir(os.path.expanduser("~"))>>>functools.reduce(operator.add,map(os.path.getsize,files))4377381在此示例中,我们调用os.path。expanduser("~")获取主目录的路径。然后在该路径上调用os.listdir()以获取所有文件路径的列表。对map()的调用使用os.path.getsize()来获取每个文件的大小。最后使用reduce()和operator.add()得到每个文件大小的累加和。最终结果是主目录中所有文件的总大小(以字节为单位)。注意:几年前,Google开发并开始使用他们称为MapReduce的编程模型。它是一种新的数据处理方式,旨在使用集群上的并行和分布式计算来管理大数据。该模型的灵感来自函数式编程中常用的map和reduce操作的组合。MapReduce模型对Google在合理时间内处理大量数据的能力产生了巨大影响。然而,到2014年,谷歌停止使用MapReduce作为他们的主要处理模型。今天,我们可以找到一些MapReduce的替代实现,例如ApacheHadoop,它是使用MapReduce模型的开源软件工具的集合。尽管reduce()可用于解决本节中涵盖的问题,但Python提供了其他工具,可以带来更Pythonic和更高效的解决方案。例如,内置函数sum()可用于计算主目录中文件的总大小。>>>importos>>>importos.path>>>files=os.listdir(os.path.expanduser("~"))>>>sum(map(os.path.getsize,files))4377381这个该示例比我们之前看到的示例更具可读性和效率。使用基于元组的可迭代对象和starmap()Python的itertools.starmap()生成一个迭代器,该迭代器将函数应用于从可迭代元组获得的参数并生成结果。在处理已经分组在元组中的可迭代对象时,它很有用。map()和starmap()之间的主要区别在于后者使用解包运算符(*)调用其转换函数,将每个元组参数解包为多个位置参数。因此,转换函数称为function(*args)而不是function(arg1,arg2,...argN)。starmap()的官方文档[1]说该函数大致相当于下面的Python函数。defstarmap(function,iterable):forargsiniterable:yieldfunction(*args)此函数中的for循环迭代iterable中的项目,并作为结果产生转换后的项目。对function(*args)的调用使用解包运算符将原语解包为几个位置参数。以下是starmap()工作原理的一些示例。该函数中的for循环遍历iterable中的元素,得到转换后的结果。调用function(*args)以使用解包运算符将元组解包为多个位置参数。以下是starmap()工作原理的一些示例:>>>fromitertoolsimportstarmap>>>list(starmap(pow,[(2,7),(4,3)]))[128,64]>>>list(starmap(ord,[(2,7),(4,3)]))Traceback(最后一次调用):文件“”,第1行,inlist(starmap(ord,[(2,7),(4,3)]))TypeError:ord()takesexactlyoneargument(2given)在第一个示例中,使用pow()计算每个元组中第一个参数的次幂值的第二个值。这些元组的形式为(base,exponent)。如果可迭代对象中的每个元组都有两个元素,那么函数也必须接受两个参数。如果元组有三个元素,那么函数必须接受三个参数,依此类推。否则你会得到一个TypeError。如果您使用map()而不是starmap(),您将得到不同的结果,因为map()从每个元组中提取一项。>>>list(map(pow,(2,7),(4,3)))[16,343]请注意,map()采用两个元组,而不是一个元组的列表。map()还在每次迭代中从每个元组中获取一个值。为了使map()返回与starmap()相同的结果,需要交换值。>>>list(map(pow,(2,4),(7,3)))[128,64]在这种情况下,我们有一个包含两个元组而不是一个元组的列表,还交换了7和4。现在,第一个元组提供基数,第二个元组提供指数。用Pythonic编码替换map()像map()、filter()和reduce()这样的函数式编程工具已经存在了很长时间。然而,几乎在每个用例中,列表推导式和生成器表达式已成为它们的自然替代品。例如,map()提供的功能几乎总是用列表理解或生成器表达式更好地表达。在接下来的两节中,我们将学习如何用列表理解或生成器表达式替换对map()的调用,从而使我们的代码更具可读性和Pythonic。有一个使用列表推导式的通用模式,我们可以用列表推导式替换对map()的调用。具体方法如下。#使用map生成列表list(map(function,iterable))#使用列表理解生成列表[function(x)forxiniterable]。请注意,列表理解几乎总是比调用map()更易读。由于列表推导式在Python开发人员中非常流行,因此您可以在任何地方找到它们。因此,用列表理解替换map()调用将使您的代码对其他Python开发人员来说更加熟悉。下面是一个示例,说明如何使用列表理解而不是map()来构建平方数列表。>>>#转换函数>>>defsquare(number):...returnnumber**2>>>numbers=[1,2,3,4,5,6]>>>#usemap()>>>list(map(square,numbers))[1,4,9,16,25,36]>>>#使用列表理解>>>[square(x)forxinnumbers][1,4,9,16,25,36]如果我们比较这两个解决方案,那么我们可能会说使用列表理解的那个更具可读性,因为它读起来几乎像普通英语。此外,列表理解避免在map()上显式调用list()来构建最终列表。使用生成器表达式map()返回一个映射对象,它是一个按需生成项目的迭代器。因此,生成器表达式[2]是map()的自然替代品,因为生成器表达式返回生成器对象,这些对象也是按需生成项的迭代器。map()返回一个地图对象,它是一个按需生成项目的迭代器。所以map()的自然替代是生成器表达式,因为生成器表达式返回生成器对象,这些对象也是按需生成项的迭代器。众所周知,Python迭代器在内存消耗方面非常高效。这就是map()现在返回迭代器而不是列表的原因。列表理解和生成器表达式之间存在细微的语法差异。第一种方法使用一对方括号('[]')分隔表达式。第二个使用一对括号('()')。因此,要将列表理解转换为生成器表达式,只需将方括号替换为圆括号即可。您可以使用生成器表达式编写比使用map()的代码更易读的代码。请看下面的例子。>>>#转换函数>>>defsquare(number):...returnnumber**2>>>numbers=[1,2,3,4,5,6]>>>#usemap()>>>map_obj=map(square,numbers)>>>map_obj<0x7f254d180a60处的地图对象>>>>list(map_obj)[1,4,9,16,25,36]>>>#使用生成器表达式>>>gen_exp=(square(x)forxinnumbers)>>>gen_expat0x7f254e056890>>>>list(gen_exp)[1,4,9,16,25,36]这一段有代码与上一节代码的一个主要区别:将方括号改为一对圆括号,将列表推导式变为生成器表达式。生成器表达式通常用作函数调用中的参数。在这种情况下,不需要使用括号来创建生成器表达式,因为用于调用函数的括号也提供了构建生成器的语法。考虑到这一点,您可以通过像这样调用list()来获得与上述示例相同的结果:>>>list(square(x)forxinnumbers)[1,4,9,16,25,36]如果在函数调用中使用生成器表达式作为参数,则不需要额外的一对括号。我们用来调用函数的括号提供了构建生成器的语法。生成器表达式在内存消耗方面与map()一样高效,因为它们都返回按需生成项目的迭代器。然而,生成器表达式几乎总能提高代码的可读性。它们还使您的代码在其他Python开发人员看来更像Pythonic。总结我们可以使用Python的map()对可迭代对象进行映射操作。映射操作包括将转换函数应用于可迭代对象中的元素以生成转换后的可迭代对象。通常,map()可以在不使用显式循环的情况下处理和转换可迭代对象。在本文中,我们了解了map()的工作原理以及如何使用它来处理可迭代对象。还了解了一些可以在您的代码中替换map()的python工具。至此,我们现在知道如何:使用Python的map()在不使用显式循环的情况下使用map()处理和转换可迭代对象推导式和生成器表达式来执行复杂的转换有了这些新知识,您将能够在您的代码中使用map()并以函数式编程风格处理它。也可以通过用列表理解或生成器表达式替换map()来切换到更pythonic和现代的风格。参考资料[1]starmap官方文档:https://docs.python.org/3/library/itertools.html#itertools.starmap[2]生成器表达式:https://realpython.com/introduction-to-python-发电机/#building-generators-with-generator-expressions