【问题标题】:List comprehension vs map列表理解与地图
【发布时间】:2010-11-17 20:43:06
【问题描述】:

是否有理由更喜欢使用map() 而不是列表理解,反之亦然?它们中的任何一个通常比另一个更有效还是被认为通常更pythonic?

【问题讨论】:

  • 请注意,如果您使用映射而不是列表解析,PyLint 会发出警告,请参阅message W0141
  • @lumbric,我不确定,但只有在地图中使用 lambda 时才会这样做。
  • 我做了一个 17 分钟的 list comp vs map 教程,如果有人觉得有用的话 - youtube.com/watch?v=hNW6Tbp59HQ

标签: python list-comprehension map-function


【解决方案1】:

map 在某些情况下可能在微观上更快(当您不是为此目的制作 lambda,而是在 map 和 listcomp 中使用相同的函数时)。在其他情况下,列表推导可能更快,并且大多数(不是全部)pythonista 认为它们更直接和更清晰。

使用完全相同的功能时地图的微小速度优势的示例:

$ python -m timeit -s'xs=range(10)' 'map(hex, xs)'
100000 loops, best of 3: 4.86 usec per loop
$ python -m timeit -s'xs=range(10)' '[hex(x) for x in xs]'
100000 loops, best of 3: 5.58 usec per loop

当 map 需要 lambda 时性能比较如何完全颠倒的示例:

$ python -m timeit -s'xs=range(10)' 'map(lambda x: x+2, xs)'
100000 loops, best of 3: 4.24 usec per loop
$ python -m timeit -s'xs=range(10)' '[x+2 for x in xs]'
100000 loops, best of 3: 2.32 usec per loop

【讨论】:

  • 是的,确实,我们的内部 Python 风格指南在工作中明确建议 listcomps 与 map 和 filter(甚至没有提到在某些情况下可以提供微小但可测量的性能改进 map;-)。
  • 不要对 Alex 的无限风格点进行 kibash,但有时 map 对我来说似乎更容易阅读:data = map(str, some_list_of_objects)。其他一些... operator.attrgetter、operator.itemgetter 等。
  • map(operator.attrgetter('foo'), objs)[o.foo for o in objs] 更容易阅读?!
  • @Alex:我不想在这里引入不必要的名字,比如o,你的例子说明了原因。
  • 我认为@GreggLind 有一个观点,不过以他的str() 为例。
【解决方案2】:

我发现列表推导通常比 map 更能表达我想要做的事情 - 他们都完成了它,但前者节省了试图理解可能是复杂的 lambda 表达式的精神负担.

还有一个采访在某处(我不能随便找到),Guido 将lambdas 和函数式函数列为他最后悔接受 Python 的事情,所以你可以提出他们的论点正因为如此,它不是 Pythonic。

【讨论】:

  • 是的,叹息,但是 Guido 最初打算在 Python 3 中完全删除 lambda,但遭到了反对的一连串游说,所以尽管我大力支持,但他还是回去了——嗯,猜 lambda 也是在许多 SIMPLE 情况下很方便,唯一的问题是当它超出 SIMPLE 的范围或被分配给一个名称时(在后一种情况下,它是 def! -)。
  • 你正在考虑的采访是这个:amk.ca/python/writing/gvr-interview,Guido 说 “有时我接受贡献太快了,后来意识到这是一个错误。一个例如一些函数式编程特性,例如 lambda 函数。lambda 是一个关键字,可让您创建一个小型匿名函数;内置函数(如 map、filter 和 reduce)在序列类型上运行函数,例如一个列表。”
  • @Alex,我没有你多年的经验,但我见过比 lambda 复杂得多的列表理解。当然,滥用语言特性始终是一种难以抗拒的诱惑。有趣的是,列表推导(根据经验)似乎比 lambdas 更容易被滥用,尽管我不确定为什么会这样。我还要指出,“步履蹒跚”并不总是一件坏事。缩小“这条线可能正在做的事情”的范围有时可以让读者更容易理解。例如,C++ 中的 const 关键字在这些方面取得了巨大的成功。
  • > 圭多。这是圭多疯了的另一个证据。当然lambda 已经做得很蹩脚(没有声明......)以至于它们很难使用并且无论如何都受到限制。
【解决方案3】:

这是一种可能的情况:

map(lambda op1,op2: op1*op2, list1, list2)

对比:

[op1*op2 for op1,op2 in zip(list1,list2)]

如果您坚持使用列表推导而不是地图,我猜 zip() 是一个不幸且不必要的开销,您需要沉迷其中。如果有人能肯定或否定地澄清这一点,那就太好了。

【讨论】:

  • "[op1*op2 from op1,op2 in zip(list1,list2)]" | s/form/for/ 和一个没有 zip 的等效列表:(可读性较差)[list1[i]*list2[i] for i in range(len(list1))]
  • 在你的第二个代码引用 @andz 和 @weakish 的评论中应该是“for”而不是“from”。我以为我发现了一种新的列表推导语法方法......该死。
  • 添加一个很晚的评论,你可以使用itertools.izip使zip变得懒惰
  • 我想我还是更喜欢map(operator.mul, list1, list2)。正是在这些非常简单的左侧表达式上,理解变得笨拙。
  • 我没有意识到 map 可以将多个迭代作为其功能的输入,因此可以避免 zip。
【解决方案4】:

案例

  • 常见情况:几乎总是,您会希望在 python 中使用列表推导式,因为对于阅读您的代码的新手程序员来说,您正在做的事情会更加明显。 (这不适用于其他语言,其他成语可能适用。)你对 python 程序员所做的事情会更加明显,因为列表推导是 python 中用于迭代的事实上的标准;他们是预期的
  • 不太常见的情况:但是,如果您已经定义了一个函数,那么使用map 通常是合理的,尽管它被认为是“unpythonic”。例如,map(sum, myLists)[sum(x) for x in myLists] 更优雅/简洁。您获得了不必编写虚拟变量(例如sum(x) for x...sum(_) for _...sum(readableName) for readableName...)的优雅,您必须输入两次,只是为了迭代。 filterreduce 以及 itertools 模块中的任何内容都适用相同的论点:如果您已经有一个方便的函数,您可以继续进行一些函数式编程。这在某些情况下获得了可读性,而在其他情况下则失去了可读性(例如,新手程序员、多个参数)......但无论如何,您的代码的可读性很大程度上取决于您的 cmets。
  • 几乎从不:在进行函数式编程时,您可能希望将map 函数用作纯抽象函数,在其中映射map,或currying map,或从交谈中受益关于map 作为一个函数。例如,在 Haskell 中,名为fmap 的函子接口泛化了任何数据结构上的映射。这在 python 中非常少见,因为 python 语法迫使你使用生成器风格来谈论迭代;你不能轻易概括它。 (这有时好有时坏。)您可能会想出一些罕见的 python 示例,其中map(f, *lists) 是一个合理的做法。我能想到的最接近的例子是sumEach = partial(map,sum),它是一个单行代码,大致相当于:

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • 只使用for-loop:当然你也可以只使用for循环。虽然从函数式编程的角度来看并不那么优雅,但有时​​非局部变量会使命令式编程语言(如 python)中的代码更清晰,因为人们非常习惯以这种方式阅读代码。通常,当您仅执行任何复杂操作时,for 循环也是最有效的在内存方面效率很高(不一定在时间方面,我希望在最坏的情况下是一个恒定因素,除非出现一些罕见的病态垃圾收集打嗝)。

“Python主义”

我不喜欢“pythonic”这个词,因为我发现pythonic 在我眼中并不总是优雅的。尽管如此,mapfilter 以及类似的函数(比如非常有用的 itertools 模块)在风格上可能被认为是不符合 Python 的。

懒惰

就效率而言,像大多数函数式编程结构一样,MAP CAN BE LAZY,实际上在 python 中是惰性的。这意味着您可以这样做(在 python3 中)并且您的计算机不会耗尽内存并丢失所有未保存的数据:

>>> map(str, range(10**100))
<map object at 0x2201d50>

尝试通过列表理解来做到这一点:

>>> [str(n) for n in range(10**100)]
# DO NOT TRY THIS AT HOME OR YOU WILL BE SAD #

请注意,列表推导本质上也是惰性的,但 python 选择将它们实现为非惰性。尽管如此,python 确实支持生成器表达式形式的惰性列表推导,如下所示:

>>> (str(n) for n in range(10**100))
<generator object <genexpr> at 0xacbdef>

您基本上可以将[...] 语法视为将生成器表达式传递给列表构造函数,例如list(x for x in range(5))

简单的人为示例

from operator import neg
print({x:x**2 for x in map(neg,range(5))})

print({x:x**2 for x in [-y for y in range(5)]})

print({x:x**2 for x in (-y for y in range(5))})

列表推导是非惰性的,因此可能需要更多内存(除非您使用生成器推导)。方括号[...] 经常使事情变得显而易见,尤其是在括号乱七八糟的情况下。另一方面,有时您最终会变得冗长,例如输入[x for x in...。只要您保持迭代器变量简短,如果您不缩进代码,列表推导通常会更清晰。但是你总是可以缩进你的代码。

print(
    {x:x**2 for x in (-y for y in range(5))}
)

或者分手:

rangeNeg5 = (-y for y in range(5))
print(
    {x:x**2 for x in rangeNeg5}
)

python3的效率比较

map 现在懒惰了:

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=map(f,xs)'
1000000 loops, best of 3: 0.336 usec per loop            ^^^^^^^^^

因此,如果您不会使用所有数据,或者不提前知道需要多少数据,python3 中的map(以及 python2 或 python3 中的生成器表达式)将避免计算它们的值,直到必要的最后一刻。通常这通常会超过使用map 的任何开销。不利的一面是,与大多数函数式语言相比,这在 python 中非常有限:只有在“按顺序”从左到右访问数据时才能获得此好处,因为 python 生成器表达式只能按x[0], x[1], x[2], ... 的顺序进行评估.

但是,假设我们有一个预制函数f,我们想要map,我们忽略map 的惰性,立即强制使用list(...) 进行评估。我们得到了一些非常有趣的结果:

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=list(map(f,xs))'                                                                                                                                                
10000 loops, best of 3: 165/124/135 usec per loop        ^^^^^^^^^^^^^^^
                    for list(<map object>)

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=[f(x) for x in xs]'                                                                                                                                      
10000 loops, best of 3: 181/118/123 usec per loop        ^^^^^^^^^^^^^^^^^^
                    for list(<generator>), probably optimized

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=list(f(x) for x in xs)'                                                                                                                                    
1000 loops, best of 3: 215/150/150 usec per loop         ^^^^^^^^^^^^^^^^^^^^^^
                    for list(<generator>)

结果采用 AAA/BBB/CCC 的形式,其中 A 是在 2010 年左右的 Intel 工作站上使用 python 3.?.? 执行的,B 和 C 是在 2013 年左右的 AMD 工作站上使用 python 3.2 执行的.1,具有极其不同的硬件。结果似乎是地图和列表推导在性能上具有可比性,这受其他随机因素的影响最大。奇怪的是,我们唯一能说的似乎是,虽然我们期望列表推导 [...] 比生成器表达式 (...) 执行得更好,但 map 也比生成器表达式更有效(再次假设所有值都被评估/使用)。

重要的是要认识到这些测试假设一个非常简单的函数(恒等函数);但是这很好,因为如果函数很复杂,那么与程序中的其他因素相比,性能开销可以忽略不计。 (用 f=lambda x:x+x 等其他简单的东西进行测试可能仍然很有趣)

如果你擅长阅读 python 汇编,你可以使用 dis 模块来看看这是否真的是在幕后发生的事情:

>>> listComp = compile('[f(x) for x in xs]', 'listComp', 'eval')
>>> dis.dis(listComp)
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x2511a48, file "listComp", line 1>) 
              3 MAKE_FUNCTION            0 
              6 LOAD_NAME                0 (xs) 
              9 GET_ITER             
             10 CALL_FUNCTION            1 
             13 RETURN_VALUE         
>>> listComp.co_consts
(<code object <listcomp> at 0x2511a48, file "listComp", line 1>,)
>>> dis.dis(listComp.co_consts[0])
  1           0 BUILD_LIST               0 
              3 LOAD_FAST                0 (.0) 
        >>    6 FOR_ITER                18 (to 27) 
              9 STORE_FAST               1 (x) 
             12 LOAD_GLOBAL              0 (f) 
             15 LOAD_FAST                1 (x) 
             18 CALL_FUNCTION            1 
             21 LIST_APPEND              2 
             24 JUMP_ABSOLUTE            6 
        >>   27 RETURN_VALUE

 

>>> listComp2 = compile('list(f(x) for x in xs)', 'listComp2', 'eval')
>>> dis.dis(listComp2)
  1           0 LOAD_NAME                0 (list) 
              3 LOAD_CONST               0 (<code object <genexpr> at 0x255bc68, file "listComp2", line 1>) 
              6 MAKE_FUNCTION            0 
              9 LOAD_NAME                1 (xs) 
             12 GET_ITER             
             13 CALL_FUNCTION            1 
             16 CALL_FUNCTION            1 
             19 RETURN_VALUE         
>>> listComp2.co_consts
(<code object <genexpr> at 0x255bc68, file "listComp2", line 1>,)
>>> dis.dis(listComp2.co_consts[0])
  1           0 LOAD_FAST                0 (.0) 
        >>    3 FOR_ITER                17 (to 23) 
              6 STORE_FAST               1 (x) 
              9 LOAD_GLOBAL              0 (f) 
             12 LOAD_FAST                1 (x) 
             15 CALL_FUNCTION            1 
             18 YIELD_VALUE          
             19 POP_TOP              
             20 JUMP_ABSOLUTE            3 
        >>   23 LOAD_CONST               0 (None) 
             26 RETURN_VALUE

 

>>> evalledMap = compile('list(map(f,xs))', 'evalledMap', 'eval')
>>> dis.dis(evalledMap)
  1           0 LOAD_NAME                0 (list) 
              3 LOAD_NAME                1 (map) 
              6 LOAD_NAME                2 (f) 
              9 LOAD_NAME                3 (xs) 
             12 CALL_FUNCTION            2 
             15 CALL_FUNCTION            1 
             18 RETURN_VALUE 

似乎使用[...] 语法比使用list(...) 更好。遗憾的是,map 类对反汇编来说有点不透明,但我们可以通过速度测试来完成。

【讨论】:

  • “就风格而言,非常有用的 itertools 模块 [is] 可能被认为是 unpythonic”。唔。我也不喜欢“Pythonic”这个词,所以从某种意义上说,我不在乎它的含义,但我认为根据“Pythonicness”内置函数@ 987654374@ 和 filter 以及标准库 itertools 本质上是不好的风格。除非 GvR 实际上说它们要么是一个可怕的错误,要么只是为了性能,如果这就是“Pythonicness”所说的,唯一自然的结论就是把它当作愚蠢的东西忘记 ;-)
  • @SteveJessop:实际上,Guido thought dropping map/filter was a great idea for Python 3,只有其他 Pythonistas 的反叛将它们保留在内置命名空间中(而 reduce 被移动到 functools)。我个人不同意(mapfilter 可以使用预定义的,特别是内置的函数,如果需要 lambda,则永远不要使用它们),但多年来 GvR 基本上称它们不是 Pythonic。跨度>
  • @ShadowRanger: 是的,但 GvR 是否曾计划删除 itertools?我从这个答案中引用的部分是让我感到困惑的主要主张。我不知道在他的理想世界中,mapfilter 是否会转移到 itertools(或 functools)或完全消失,但无论哪种情况,一旦有人说 itertools 是 unPythonic in它的全部,那么我真的不知道“Pythonic”应该是什么意思,但我认为它不会类似于“GvR 建议人们使用的东西”。
  • @SteveJessop:我只是针对map/filter,而不是itertools。函数式编程完全是 Pythonic(itertoolsfunctoolsoperator 都是专门为函数式编程而设计的,我一直在 Python 中使用函数式习语),而 itertools 提供的特性会很痛苦为了实现自己,特别是 mapfilter 与生成器表达式的冗余使 Guido 讨厌它们。 itertools 一直都很好。
  • 如果有办法,我可以收藏这个答案。解释得很好。
【解决方案5】:

Python 2:您应该使用 mapfilter 而不是列表推导式。

即使它们不是“Pythonic”,您也应该喜欢它们的客观原因是:
它们需要函数/lambdas 作为参数,这引入了一个新的作用域

我不止一次被这个咬过:

for x, y in somePoints:
    # (several lines of code here)
    squared = [x ** 2 for x in numbers]
    # Oops, x was silently overwritten!

但如果我说过:

for x, y in somePoints:
    # (several lines of code here)
    squared = map(lambda x: x ** 2, numbers)

那么一切都会好起来的。

你可以说我在同一范围内使用相同的变量名是愚蠢的。

我不是。代码原本没问题——两个x不在同一个范围内。
只是在我将内部块移动到代码的不同部分之后才出现问题(阅读:维护期间的问题,而不是开发期间的问题),我没想到会这样。

是的,如果你从不犯这个错误,那么列表推导会更优雅。
但是根据个人经验(以及看到其他人犯同样的错误),我已经看到它发生了足够多的次数,我认为当这些错误潜入您的代码时,您不值得忍受痛苦。

结论:

使用mapfilter。它们可以防止微妙的难以诊断的范围相关错误。

旁注:

不要忘记考虑使用imapifilter(在itertools)如果它们适合您的情况!

【讨论】:

  • 感谢您指出这一点。我没有明确意识到列表理解在同一范围内并且可能是一个问题。话虽如此,我认为其他一些答案清楚地表明列表理解应该是大多数时候的默认方法,但这是要记住的。这也是一个很好的一般提醒,以保持函数(以及范围)小,并进行彻底的单元测试并使用断言语句。
  • @wim:这只是关于 Python 2,尽管如果你想保持向后兼容,它也适用于 Python 3。我知道这件事,并且我已经使用 Python 有一段时间了(是的,不仅仅是几个月),但它发生在我身上。我见过比我聪明的其他人落入同样的陷阱。如果你是如此聪明和/或经验丰富,这对你来说不是问题,那么我为你感到高兴,我认为大多数人都不像你。如果是这样,就不会有在 Python 3 中修复它的冲动。
  • 很抱歉,你是在 2012 年底写的,在 python 3 出现之后,答案读起来就像你推荐一种不受欢迎的 python 编码风格,只是因为你被咬了剪切和粘贴代码时出现错误。我从来没有声称自己聪明或经验丰富,我只是不同意你的理由可以证明这种大胆的说法是合理的。
  • @wim:嗯? Python 2 仍然在很多地方使用,Python 3 的存在并没有改变这一点。当你说“对于使用 Python 超过几个月的人来说,这并不是一个微妙的错误”时,这句话的字面意思是“这只涉及没有经验的开发人员”(显然不是你)。为了记录,您显然没有阅读答案,因为我用 bold 表示我是在 移动,而不是复制代码。跨语言的复制粘贴错误非常统一。由于其作用域,这种错误对于 Python 来说更为独特;它更微妙,更容易忘记和错过。
  • 切换到map 和/或filter 仍然不是一个合乎逻辑的理由。如果有的话,避免您的问题的最直接和合乎逻辑的翻译不是map(lambda x: x ** 2, numbers),而是不会泄漏的生成器表达式list(x ** 2 for x in numbers),正如JeromeJ 已经指出的那样。看 Mehrdad,不要这么个人化地投反对票,我只是强烈不同意你在这里的推理。
【解决方案6】:

实际上,map 和列表推导在 Python 3 语言中的行为完全不同。看看下面的 Python 3 程序:

def square(x):
    return x*x
squares = map(square, [1, 2, 3])
print(list(squares))
print(list(squares))

您可能希望它会打印两次“[1,4,9]”行,但它会打印“[1,4,9]”,然后是“[]”。第一次查看 squares 时,它的行为似乎是三个元素的序列,但第二次是一个空元素。

在 Python 2 语言中,map 返回一个普通的旧列表,就像两种语言中的列表推导式一样。关键在于 Python 3 中 map 的返回值(以及 Python 2 中的 imap)不是一个列表——它是一个迭代器!

与迭代列表不同,迭代迭代器时会消耗元素。这就是为什么squares 在最后的print(list(squares)) 行中看起来是空的。

总结一下:

  • 在处理迭代器时,您必须记住它们是有状态的,并且在您遍历它们时它们会发生变化。
  • 列表更具可预测性,因为它们仅在您显式改变它们时才会发生变化;它们是容器
  • 还有一个好处:数字、字符串和元组更加可预测,因为它们根本无法改变;它们是

【讨论】:

  • 这可能是列表推导的最佳论据。 pythons map 不是功能映射,而是功能实现的残缺的红发继子。很伤心,因为我真的不喜欢理解。
  • @semiomant 我会说惰性映射(如在 python3 中)比渴望映射(如在 python2 中)更“实用”。例如,Haskell 中的 map 是惰性的(好吧,Haskell 中的一切都是惰性的……)。无论如何,惰性映射更适合链接映射 - 如果您有一个应用于映射的映射应用于映射,那么您在 python2 中为每个中间映射调用都有一个列表,而在 python3 中您只有一个结果列表,因此它的内存效率更高.
  • 我想我想要的是让map 生成一个数据结构,而不是一个迭代器。但也许惰性迭代器比惰性数据结构更容易。深思熟虑。谢谢@MnZrK
  • 你想说 map 返回一个可迭代的,而不是一个迭代器。
【解决方案7】:

如果您计划编写任何异步、并行或分布式代码,您可能更喜欢map 而不是列表推导式——因为大多数异步、并行或分布式包都提供了map 函数来重载python 的@987654326 @。然后通过将适当的map 函数传递给您的其余代码,您可能不必修改原始串行代码以使其并行运行(等等)。

【解决方案8】:

所以由于 Python 3,map() 是一个迭代器,你需要记住你需要什么:一个迭代器或 list 对象。

正如@AlexMartelli 已经mentionedmap() 仅在您不使用lambda 函数时才比列表理解更快。

我会给你一些时间比较。

Python 3.5.2 和 CPython
我使用过 Jupiter notebook 尤其是 %timeit 内置的魔法命令
测量:s == 1000 ms == 1000 * 1000 µs = 1000 * 1000 * 1000 ns

设置:

x_list = [(i, i+1, i+2, i*2, i-9) for i in range(1000)]
i_list = list(range(1000))

内置功能:

%timeit map(sum, x_list)  # creating iterator object
# Output: The slowest run took 9.91 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 277 ns per loop

%timeit list(map(sum, x_list))  # creating list with map
# Output: 1000 loops, best of 3: 214 µs per loop

%timeit [sum(x) for x in x_list]  # creating list with list comprehension
# Output: 1000 loops, best of 3: 290 µs per loop

lambda函数:

%timeit map(lambda i: i+1, i_list)
# Output: The slowest run took 8.64 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 325 ns per loop

%timeit list(map(lambda i: i+1, i_list))
# Output: 1000 loops, best of 3: 183 µs per loop

%timeit [i+1 for i in i_list]
# Output: 10000 loops, best of 3: 84.2 µs per loop

还有生成器表达式,见PEP-0289。所以我认为将其添加到比较中会很有用

%timeit (sum(i) for i in x_list)
# Output: The slowest run took 6.66 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 495 ns per loop

%timeit list((sum(x) for x in x_list))
# Output: 1000 loops, best of 3: 319 µs per loop

%timeit (i+1 for i in i_list)
# Output: The slowest run took 6.83 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 506 ns per loop

%timeit list((i+1 for i in i_list))
# Output: 10000 loops, best of 3: 125 µs per loop

你需要list对象:

如果是自定义函数使用列表推导,如果有内置函数使用list(map())

你不需要list 对象,你只需要一个可迭代对象:

始终使用map()

【讨论】:

    【解决方案9】:

    我认为最 Pythonic 的方法是使用列表解析而不是 mapfilter。原因是列表推导比mapfilter 更清晰。

    In [1]: odd_cubes = [x ** 3 for x in range(10) if x % 2 == 1] # using a list comprehension
    
    In [2]: odd_cubes_alt = list(map(lambda x: x ** 3, filter(lambda x: x % 2 == 1, range(10)))) # using map and filter
    
    In [3]: odd_cubes == odd_cubes_alt
    Out[3]: True
    

    如您所见,理解不需要像 map 那样需要额外的 lambda 表达式。此外,推导式还允许轻松过滤,而map 需要filter 才能允许过滤。

    【讨论】:

      【解决方案10】:

      我进行了一个快速测试,比较了调用对象方法的三种方法。在这种情况下,时间差异可以忽略不计,并且与所讨论的功能有关(请参阅@Alex Martelli 的 response)。在这里,我查看了以下方法:

      # map_lambda
      list(map(lambda x: x.add(), vals))
      
      # map_operator
      from operator import methodcaller
      list(map(methodcaller("add"), vals))
      
      # map_comprehension
      [x.add() for x in vals]
      

      我查看了整数 (Python int) 和浮点数 (Python float) 的列表(存储在变量 vals 中)以增加列表大小。考虑以下虚拟类DummyNum

      class DummyNum(object):
          """Dummy class"""
          __slots__ = 'n',
      
          def __init__(self, n):
              self.n = n
      
          def add(self):
              self.n += 5
      

      具体来说,add 方法。 __slots__ 属性是 Python 中的一个简单优化,用于定义类(属性)所需的总内存,从而减少内存大小。 这是结果图。

      如前所述,所使用的技术差异很小,您应该以最易读的方式或在特定情况下进行编码。在这种情况下,列表推导(map_comprehension 技术)对于对象中的两种类型的添加是最快的,尤其是对于较短的列表。

      访问this pastebin 了解用于生成绘图和数据的来源。

      【讨论】:

      • 正如在其他答案中已经解释的那样,map 只有在以完全相同的方式调用函数时才会更快(即[*map(f, vals)][f(x) for x in vals])。所以list(map(methodcaller("add"), vals))[methodcaller("add")(x) for x in vals] 快。 map 可能不会在循环对应方使用可以避免一些开销的不同调用方法时更快(例如,x.add() 避免了methodcaller 或 lambda 表达式开销)。对于这个特定的测试用例,[*map(DummyNum.add, vals)] 会更快(因为DummyNum.add(x)x.add() 性能基本相同)。
      • 顺便说一下,显式的list() 调用比列表理解稍慢。为了公平比较,您需要写[*map(...)]
      • @GZ0 感谢您的反馈!一切都说得通,我不知道list() 调用增加了开销。应该花更多时间阅读答案。我将重新运行这些测试以进行公平比较,但差异可能可以忽略不计。
      【解决方案11】:

      我尝试了@alex-martelli 的代码,但发现了一些差异

      python -mtimeit -s "xs=range(123456)" "map(hex, xs)"
      1000000 loops, best of 5: 218 nsec per loop
      python -mtimeit -s "xs=range(123456)" "[hex(x) for x in xs]"
      10 loops, best of 5: 19.4 msec per loop
      

      即使对于非常大的范围,map 也需要相同的时间,而使用列表理解需要很多时间,这从我的代码中可以看出。所以除了被认为是“unpythonic”之外,我没有遇到任何与地图使用相关的性能问题。

      【讨论】:

      • 这是一个非常古老的问题,您所指的答案很可能是参考 Python 2 编写的,其中map 返回一个列表。在 Python 3 中,map 是惰性求值的,所以简单地调用 map 不会计算任何新的列表元素,因此你会得到这么短的时间。
      • 我认为您使用的是 Python 3.x 当我问这个问题时,Python 3 是最近才发布的,而 Python 2.x 是非常标准的。
      【解决方案12】:

      图片来源:Experfy

      您可以自己看看 - 列表理解和地图功能之间哪个更好

      (与 map 函数相比,List Comprehension 处理 100 万条记录所需的时间更少)

      希望对您有所帮助!祝你好运:)

      【讨论】:

      • 目前还不清楚运行了哪些确切的代码或样本有多大。无论如何,for-loop 和 map 之间的巨大区别似乎是不正确的。
      【解决方案13】:

      我用perfplot(我的一个项目)计时了一些结果。

      正如其他人所指出的,map 实际上只返回一个迭代器,因此它是一个恒定时间操作。当通过list() 实现迭代器时,它与列表推导相当。根据表达式的不同,任何一个都可能有轻微的优势,但几乎不重要。

      请注意,像 x ** 2 这样的算术运算在 NumPy 中要快得多,尤其是在输入数据已经是 NumPy 数组的情况下。

      hex:

      x ** 2:


      重现情节的代码:

      import perfplot
      
      
      def standalone_map(data):
          return map(hex, data)
      
      
      def list_map(data):
          return list(map(hex, data))
      
      
      def comprehension(data):
          return [hex(x) for x in data]
      
      
      b = perfplot.bench(
          setup=lambda n: list(range(n)),
          kernels=[standalone_map, list_map, comprehension],
          n_range=[2 ** k for k in range(20)],
          equality_check=None,
      )
      b.save("out.png")
      b.show()
      
      import perfplot
      import numpy as np
      
      
      def standalone_map(data):
          return map(lambda x: x ** 2, data[0])
      
      
      def list_map(data):
          return list(map(lambda x: x ** 2, data[0]))
      
      
      def comprehension(data):
          return [x ** 2 for x in data[0]]
      
      
      def numpy_asarray(data):
          return np.asarray(data[0]) ** 2
      
      
      def numpy_direct(data):
          return data[1] ** 2
      
      
      b = perfplot.bench(
          setup=lambda n: (list(range(n)), np.arange(n)),
          kernels=[standalone_map, list_map, comprehension, numpy_direct, numpy_asarray],
          n_range=[2 ** k for k in range(20)],
          equality_check=None,
      )
      b.save("out2.png")
      b.show()
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-09-02
        • 1970-01-01
        • 2017-11-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多