列表理解与地图


732

有理由更喜欢使用map()列表理解吗?反之亦然?它们中的一个通常比另一个效率更高,或者通常被认为比另一个更Python化吗?


8
请注意,如果您使用map而不是列表理解,PyLint会发出警告,请参阅消息W0141
lumbric

2
@lumbric,我不确定,但是只有在地图中使用了lambda时,它才起作用。
0xc0de

Answers:


660

map在某些情况下(如果您不是出于此目的而使用lambda,而是在map和listcomp中使用相同的函数),在微观上可能会更快。在其他情况下,列表理解可能会更快,并且大多数(并非全部)pythonista用户认为列表更直接,更清晰。

使用完全相同的函数时map的微小速度优势的一个示例:

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

当地图需要使用lambda时,如何完全颠倒性能比较的示例:

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

39
是的,确实,我们内部的Python风格指南确实在工作中明确建议对地图和过滤器使用listcomps(甚至没有提及在某些情况下可以提供的微小但可衡量的性能改进地图;-)。
Alex Martelli

46
不要嘲笑亚历克斯的无限风格,但是有时候地图似乎更容易理解:data = map(str,some_list_of_objects)。其他一些... operator.attrgetter,operator.itemgetter等
Gregg Lind

57
map(operator.attrgetter('foo'), objs)[o.foo for o in objs]?更容易阅读!
Alex Martelli

52
@Alex:我不想像o这里那样引入不必要的名称,您的示例说明了原因。
Reid Barton 2010年

29
我认为@GreggLind在他的str()例子中有一点。
Eric O Lebigot

474

案例

  • 常见情况:几乎总是,您将要在python中使用列表推导,因为对于新手程序员来说,阅读代码会更加明显。(这不适用于可能适用其他习惯用法的其他语言。)由于列表推导是python中用于迭代的事实上的标准,因此您对python程序员所做的工作甚至会更加明显。他们是预期的
  • 较少见的情况:但是,如果您已经定义了一个函数,则使用通常是合理的map,尽管它被认为是“非pythonic”的。例如,map(sum, myLists)比更加优雅/简洁[sum(x) for x in myLists]。您可以不必编写一个虚拟变量(例如sum(x) for x...or sum(_) for _...sum(readableName) for readableName...),而只需键入两次即可进行迭代,从而获得了优雅。同样的道理也适用于filterreduce从什么itertools模块:如果你已经有一个方便的功能,您可以继续前进,做一些函数式编程。在某些情况下,这会提高可读性,而在其他情况下(例如,新手程序员,多个参数),则会失去可读性。但是,无论如何,代码的可读性在很大程度上取决于注释。
  • 几乎永远不会map在进行函数编程时,您可能希望将函数用作纯抽象函数,在这种情况下您正在映射map,currying map,或者从map以函数的形式进行讨论中受益。例如,在Haskell中,一个称为functor的接口可以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模块)很可能在风格方面考虑unpythonic。

懒惰

就效率而言,就像大多数函数式编程构造一样,MAP可以是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构造函数,例如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            ^^^^^^^^^

因此,如果您将不使用所有数据,或者不提前知道需要多少数据,那么map在python3中(以及python2或python3中的生成器表达式)将避免计算它们的值,直到最后一刻。通常,这通常会超过使用带来的任何开销map。不利之处在于,与大多数功能语言相反,这在python中非常有限:只有按“顺序”从左至右访问数据时,您才能获得此好处,因为python生成器表达式只能按order求值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在带有python 3。?。?的约2010年英特尔工作站上执行,而B和C则是在python 3.2.1的约2013年AMD工作站上执行,具有截然不同的硬件。结果似乎是,地图和列表理解的性能可比,这受其他随机因素的影响最大。我们可以告诉的唯一的事情似乎是,奇怪的是,虽然我们期待列表解析[...]比生成器表达式更好地发挥(...)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该类在拆卸方面有点不透明,但是我们可以通过速度测试来确定。


5
“非常有用的itertools模块在样式方面可能被认为是非Python的”。嗯 我也不喜欢“ Pythonic”一词,所以从某种意义上讲我不在乎它的含义,但是我认为使用它的人并不公平,而是根据“ Pythonicness”内置函数mapfilter以及标准库itertools本身就是不好的样式。除非GvR实际上是说这是一个可怕的错误,或者仅仅是性能方面的错误,否则“ Pythonicness”所说的唯一自然的结论就是忘记它是愚蠢的;-)
Steve Jessop 2014年

4
@SteveJessop:实际上,Guido认为drop map/ filter对于Python 3是个好主意,只有其他Pythonista的反叛才将它们保留在内置名称空间中(而reduce移至functools)。我个人不同意(map并且filter对预定义的(特别是内置的)函数很好,只是lambda在需要时不要使用它们),但是GvR多年来一直将它们称为Pythonic。
ShadowRanger 2015年

@ShadowRanger:是的,但是GvR是否打算删除itertools?我从这个答案中引用的部分是使我困惑的主要主张。我不知道他是否会理想化,map并且filter会搬到itertools(或functools)或完全搬走,但是无论哪种情况,一旦有人说这itertools完全是非Python的,那么我真的不知道什么是“ Pythonic”应该是指,但我不认为它可以类似于“ GvR建议人们使用的东西”。
史蒂夫·杰索普

2
@SteveJessop:我只是在map/ filter而不是itertools。函数式编程是完全符合Python( itertoolsfunctools并且operator都在考虑函数式编程专门设计的,我用Python中所有的时间功能成语),并且itertools提供的功能,这将是一个痛苦自己实现,它是专门mapfilter用生成器表达式是多余的这让Guido讨厌他们。itertools一直很好。
ShadowRanger 2015年

1
如果可以的话,我可能会喜欢这个答案。好解释。
NelsonGon

95

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

即使它们不是“ Pythonic”的,您还是还是偏爱它们的一个客观原因是:
它们需要函数/ lambda作为参数,从而引入了新的作用域

我被这个不止一次地咬了:

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


7
感谢您指出了这一点。在我看来,列表理解并没有在相同的范围内出现并且可能是一个问题。话虽如此,我认为其他一些答案也清楚地表明,列表理解在大多数情况下应该是默认方法,但这是要记住的事情。这也是一个很好的一般性提醒,它应使函数(以及范围)保持较小,并进行全面的单元测试并使用assert语句。
TimothyAWiseman

13
@wim:这仅与Python 2有关,但如果您想保持向后兼容,则适用于Python 3。我知道这一点,并且我已经使用Python一段时间了(是的,不仅仅是几个月),但是它发生在我身上。我看到其他比我聪明的人陷入了同样的陷阱。如果您是如此聪明和/或经验丰富,以至于这对您来说不是问题,那么我为您感到高兴,我认为大多数人都不喜欢您。如果他们成功了,就不会有这样的冲动,解决它在Python 3
user541686

12
抱歉,您是在2012年底写的,就在python 3出现之后,答案看起来像是您建议使用一种原本不受欢迎的python编码风格,只是因为您在剪切和/或粘贴代码。我从未声称自己是聪明或经验丰富的人,我只是不同意大胆的说法是由您的理由来证明的。
2013年

8
@wim:嗯?Python 2仍在很多地方使用,Python 3的存在并不能改变这一事实。当您说“对于使用Python超过几个月的人来说,这并不是一个微妙的错误”时,这句话的意思是“这只涉及没有经验的开发人员”(显然不是您)。记录下来,您显然没有读答案,因为我大胆地说我正在移动而不是复制代码。跨语言的复制粘贴错误非常统一。由于其作用域,这种错误在Python上更为独特。它微妙且容易忘记和错过。
user541686 2013年

3
切换到map和/或仍然不是逻辑原因filter。正如JeromeJ所指出的,如果有的话,避免您的问题的最直接,最合理的翻译方法不是map(lambda x: x ** 2, numbers)生成器表达式list(x ** 2 for x in numbers),而是不会泄漏的生成器表达式。快看Mehrdad,不要这么个人投票,我只是强烈不同意您在这里的推理。
2013年

46

实际上,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返回一个普通的旧列表,就像列表推导在两种语言中一样。症结在于,mapPython 3(和imapPython 2)中的return值不是一个列表-它是一个迭代器!

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

总结一下:

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

这可能是列表理解的最佳论据。pythons映射不是功能映射,而是功能实现的残缺的红发继子。很伤心,因为我真的不喜欢理解。
semiomant

@semiomant我想说的是,懒惰的地图(如python3)比渴望的地图(如python2)更具“功能性”。例如,Haskell中的地图是惰性的(嗯,Haskell中的所有东西都是惰性的...)。无论如何,惰性地图更适合链接地图-如果您将一个地图应用到要应用到地图的地图,则在python2中每个中间地图调用都有一个列表,而在python3中您只有一个结果列表,因此其内存效率更高。
MnZrK

我想我想要的是map产生数据结构,而不是迭代器。但是也许惰性迭代器比惰性数据结构更容易。值得深思。谢谢@MnZrK
Semiomant '17

您想说map返回的是可迭代的,而不是迭代器。
user541686 '18

16

我发现列表理解通常比我要表达的要表达的要多map-它们都可以完成,但是前者节省了试图理解什么可能是复杂lambda表达的精神负担。

在某个地方(我无法找到它)也有一次采访,其中Guido列出了lambdas和函数功能,这是他最后悔接受Python的事情,因此您可以凭借这些观点认为它们是非Python的其中。


9
是的,长叹一口气,但是Guido最初在Python 3中完全删除lambda的初衷遭到了游说的反对,因此尽管有我的坚定支持,他还是继续使用它-嗯,猜想lambda在许多简单的案例中都太方便了,唯一的问题是当它超出SIMPLE的范围或被分配给一个名称时(在后一种情况下,它是def!-的愚蠢的重复副本)。
亚历克斯·马丁里

1
您正在考虑的采访是这样的:amk.ca/python/writing/gvr-interview,Guido在其中说:“有时候我接受捐款太快了,后来才意识到这是一个错误。一个例子就是一些函数式编程功能(例如lambda函数)lambda是使您可以创建小型匿名函数的关键字;内置函数(例如map,filter和reduce)可以在序列类型(如列表)上运行该函数。 ”
J. Taylor

3
@Alex,我没有您的工作经验,但是与lambda相比,我看到的列表理解要复杂得多。当然,滥用语言功能始终是难以抗拒的诱惑。有趣的是,列表理解(凭经验)似乎比lambda更容易被滥用,尽管我不确定为什么会这样。我还要指出,“陷入困境”并不总是一件坏事。缩小“这行可能正在做的事情”的范围有时会使读者更容易理解。例如,在这些方面const,C ++中的关键字取得了巨大的成功。
斯图尔特·伯格

>圭多。这是吉多不在他脑海中的另一条证据。当然,lambda已经使's me脚了(没有声明..),以致它们很难使用并且受到限制。
javadba

16

这是一种可能的情况:

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

与:

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

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


“ [[op1 * op2来自zip(list1,list2)中的op1,op2]]” | s / form / for /和没有zip的等效列表:(可读性较差)[list1 [i] * list2 [i] for i in range(len(list1))]
2010年

2
在第二个代码引号@andz和@weakish的注释中也应为“ for”而不是“ from”。我以为我发现了一种新的列表理解语法方法……达恩。
physicsmichael 2010年

4
要添加一个很晚的评论,您可以zip使用itertools.izip
tacaswell

5
我想我还是喜欢map(operator.mul, list1, list2)。在这些非常简单的左侧表达式上,理解变得笨拙。
Yann Vernier 2015年

1
我还没有意识到map可以将多个iterables作为其功能的输入,因此可以避免使用zip。
bli

16

如果您打算编写任何异步,并行或分布式代码,则您可能会更喜欢map列表理解-因为大多数异步,并行或分布式程序包都提供了map使python过载的功能map。然后,通过将适当的map函数传递给其余代码,您可能不必修改原始串行代码即可使其并行运行(等)。



1
Python的多重处理模块可以做到这一点:docs.python.org/2/library/multiprocessing.html
Robert L.

9

因此,由于Python 3 map()是迭代器,因此您需要牢记所需的东西:迭代器或list对象。

正如@AlexMartelli已经提到的那样map()仅当您不使用lambda函数时,它比列表理解要快。

我将向您介绍一些时间比较。

Python 3.5.2和CPython
我使用了Jupiter笔记本,尤其是%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()


1

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

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

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

class DummyNum(object):
    """Dummy class"""
    __slots__ = 'n',

    def __init__(self, n):
        self.n = n

    def add(self):
        self.n += 5

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

映射Python对象方法的性能

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

访问此pastebin,获取用于生成图和数据的源。


1
正如在其他答案中已经解释的那样,map仅当以完全相同的方式(即[*map(f, vals)]vs. [f(x) for x in vals])调用函数时,速度才会更快。因此list(map(methodcaller("add"), vals))比快[methodcaller("add")(x) for x in vals]map当循环的对等方使用可以避免某些开销(例如,x.add()避免the methodcaller或lambda表达式开销)的另一种调用方法时,速度可能不会更快。对于此特定测试用例,[*map(DummyNum.add, vals)]将更快(因为DummyNum.add(x)并且x.add()具有基本相同的性能)。
GZ0

1
顺便说一句,显式list()调用比列表理解要慢一些。为了进行公平的比较,您需要编写[*map(...)]
GZ0

@ GZ0感谢您的宝贵反馈!一切都说得通,我没有意识到会list()增加开销。应该花更多的时间阅读答案。我将重新运行这些测试以进行公平的比较,但差异可能微不足道。
craymichael

0

我认为最Python的方式是使用列表理解而不是mapand filter。原因是列表理解比map和更清晰filter

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

如您所见,理解并不需要额外的lambda表达式map。此外,理解还允许容易地过滤,同时map需要filter允许过滤。


0

我尝试了@ 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

映射即使在非常大的范围内也要花费相同的时间,而使用列表理解则要花费很多时间,这从我的代码中可以明显看出。因此,除了被视为“ unpythonic”之外,我还没有遇到任何与使用map有关的性能问题。


3
这是一个非常古老的问题,您所指的答案很可能是针对Python 2编写的,其中map返回了一个列表。在Python 3中,它map是惰性计算的,因此简单地调用map不会计算任何新的列表元素,因此为什么会得到如此短的时间。
kaya3 '19

我认为您使用的是Python3.x。当我问这个问题时,Python 3才刚刚发布,而Python 2.x就是非常标准的。
TimothyAWiseman '19
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.