【问题标题】:When should I use a Map instead of a For Loop?什么时候应该使用 Map 而不是 For 循环?
【发布时间】:2022-03-17 06:04:48
【问题描述】:

这与以下内容有关:(在 Python 代码中)

for i in object:
     doSomething(i)

map(doSomething, object)

两者都很容易理解,而且简短,但是有什么速度差异吗?现在,如果 doSomething 有一个返回值,我们需要检查它是否会作为 map 中的列表返回,并且在 for 循环中,我们可以创建自己的列表或一次检查一个。

for i in object:
     returnValue = doSomething(i)
     doSomethingWithReturnValue(returnValue)

returnValue = map(doSomething, object)
map(doSomethingWithReturnValue, returnValue)

现在,我觉得两者有点分歧。这两个 doSomethingWithReturnValue 函数可能会有所不同,具体取决于是在我们通过循环时动态检查它们还是在最后一次检查它们会产生不同的结果。此外,似乎 for 循环总是可以工作,可能会更慢,而地图只能在某些情况下工作。当然,我们可以通过扭曲来完成其中任何一项工作,但重点是避免这种类型的工作。

我正在寻找的是一个场景,与在性能、可读性、可维护性或实施速度方面做得好的 for 循环相比,映射函数真正大放异彩。如果答案是真的没有太大区别,那么我想知道人们在实践中何时使用其中一种,或者它是否真的完全任意并由编码标准设置,具体取决于您的机构。

谢谢!

【问题讨论】:

  • 您可以使用列表/字典/集合理解或生成器而不是地图 - 取决于 doSomething() 的作用。
  • 不要尝试初步调整性能。我总是会选择最好的可读选项。稍后的运行时将显示 showstopper 性能是否存在问题,并且您已提高速度或资源使用率。

标签: python for-loop


【解决方案1】:

map 在您想将函数应用于可迭代的每个项目并返回结果列表时很有用。这比使用 for 循环和构造列表更简单、更简洁。

for 在其他情况下通常更具可读性,在 lisp 中,有很多迭代构造基本上是使用宏和映射编写的。因此,如果map 不适合,请使用for 循环。

理论上,如果我们有一个足够智能的编译器/解释器来使用多个 cpu/处理器,那么 map 可以更快地实现,因为每个项目上的不同操作可以并行完成。不过,我认为目前情况并非如此。

【讨论】:

  • PLINQ (C#) 可以做到这一点。
  • 为什么是过去时? Lisp 充满活力。
  • 实际上map 的性能甚至在单线程中也优于for,因为循环是用 C 编写的。请参阅我的帖子进行速度测试。
  • 一般来说,map 不能自动并行化,因为每个函数调用都可能有全局副作用。 Python 2.6+ 中已经存在并行映射:multiprocessing.Pool.map()。
  • @lamamac 没错,但列表理解通常胜过地图。
【解决方案2】:

您熟悉 timeit 模块吗?以下是一些时间安排。 -s 执行一次设置,然后循环命令并记录最佳时间。

1> python -m timeit -s "L=[]; M=range(1000)" "for m in M: L.append(m*2)"
1000 loops, best of 3: 432 usec per loop

2> python -m timeit -s "M=range(1000);f=lambda x: x*2" "L=map(f,M)"
1000 loops, best of 3: 449 usec per loop

3> python -m timeit -s "M=range(1000);f=lambda x:x*2" "L=[f(m) for m in M]"
1000 loops, best of 3: 483 usec per loop

4> python -m timeit -s "L=[]; A=L.append; M=range(1000)" "for m in M: A(m*2)"
1000 loops, best of 3: 287 usec per loop    

5> python -m timeit -s "M=range(1000)" "L=[m*2 for m in M]"
1000 loops, best of 3: 174 usec per loop

请注意,除了最后两个之外,它们都相似。严重影响时序的是函数调用(L.append 或 f(x))。在 #4 中,L.append 查找已在设置中完成一次。在 #5 中使用了没有函数调用的列表组合。

【讨论】:

  • 我想你指的是我的帖子。是的,我发现了map在py3k中返回迭代器的严重问题,但我不认为timeit有什么问题,range返回迭代器所以没有把它放在设置阶段影响不大。
  • > python3 -m timeit "[m for m in range(1000)]" 10000 个循环,最好的 3:每个循环 114 微秒 > python3 -m timeit -s M=list(range(1000 )) "[m for m in M]" 10000 个循环,最好的 3:每个循环 83 usec 只构建一次列表有显着差异。
【解决方案3】:

只需使用列表推导:它们更符合 Python 风格。它们还具有类似于生成器表达式的语法,可以轻松地从一种切换到另一种。将代码转换为 py3k 时无需更改任何内容:map 在 py3k 中返回一个可迭代对象,您必须调整代码。

如果您不关心返回值,只是不命名新列表,则需要在代码中使用一次返回值,您可能会切换到生成器表达式和最后的单个列表推导。

【讨论】:

    【解决方案4】:

    这里有一个loop和map之间的实验来证明哪个更有效:

    from time import time
    
    fruit  = range(4_000_000)
    
    tic = time()
    res=[True if i % 2 == 0 else False for i in fruit]
    toc = time()
    print(toc - tic)
    
    
    tic = time()
    list(map(lambda s: s % 2 == 0, fruit))
    toc = time()
    print(toc - tic)
    
    tic = time()
    res = []
    for i in fruit:
        if i % 2 == 0:
            res.append(True)
        else:
            res.append(False)
    toc = time()
    print(toc - tic)
    

    输出:

    0.29710841178894043
    0.4369466304779053
    0.5959873199462891
    

    所以第一种方法是最有效的方法。

    【讨论】:

      【解决方案5】:

      编辑:我没有意识到在 python 3.0 之后 map 等于 itertools.imap。所以这里的结论可能不正确。明天我会在 python 2.6 上重新运行测试并发布结果。

      如果doSomething 非常“小”,map 可以比for 循环或列表理解快很多:

      # Python 3.1.1 (r311:74483, Aug 17 2009, 17:02:12) [MSC v.1500 32 bit (Intel)] on win32
      
      from timeit import timeit
      
      do = lambda i: i+1
      
      def _for():
        for i in range(1000):
          do(i)
      
      def _map():
        map(do, range(1000))
      
      def _list():
        [do(i) for i in range(1000)]
      
      timeit(_for, number=10000)  # 2.5515936921388516
      timeit(_map, number=10000)  # 0.010167432629884843
      timeit(_list, number=10000) # 3.090125159839033
      

      这是因为map 是用C 编写的,而for 循环和列表理解是在python 虚拟机中运行的。

      【讨论】:

      • 我不知道你从哪里得到你的数字,但在我的情况下(Python 2.6)for 循环更快,大约 5%。您的代码甚至不正确,因为 _list 的迭代次数较少。您得到的巨大差异表明您的设置存在严重问题。
      • 这太荒谬了。您的代码根本不等效。阅读我的答案。即使没有地图对象与列表二分法,您的代码也不同,很遗憾您没有看到它
      • 抱歉,在我复制代码并进行格式化时出现了一些拼写错误。我在我的 Core Duo 4300 PC 上运行 python 3.1.1,map 明显优于其他两个。
      • 我在 python 2.6.4 中运行此代码进行 number=100000 次迭代,我得到大约 15 次 _for、18 次 _map 和 17 次 _list。
      • _map 返回一个生成器,这就是为什么这么快... do list(_map(..)) 这将是实时的
      猜你喜欢
      • 2011-09-04
      • 2013-01-17
      • 2014-10-20
      • 2019-06-15
      • 2011-11-12
      • 2011-06-22
      • 2015-12-11
      • 2012-02-12
      • 2012-06-27
      相关资源
      最近更新 更多