ChatGPT解决这个技术问题 Extra ChatGPT

列表理解与地图

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

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

j
juanpa.arrivillaga

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

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

$ 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() 示例。
n
ninjagecko

案例

常见情况:几乎总是,您会希望在 python 中使用列表推导,因为对于阅读您的代码的新手程序员来说,您正在做的事情会更加明显。 (这不适用于其他语言,其他习惯用法可能适用。)你对 python 程序员所做的事情会更加明显,因为列表推导是 python 中用于迭代的事实上的标准;他们是意料之中的。

不太常见的情况:但是,如果您已经定义了一个函数,那么使用 map 通常是合理的,尽管它被认为是“unpythonic”。例如,map(sum, myLists) 比 [sum(x) for x in myLists] 更优雅/简洁。您无需编写必须输入的虚拟变量(例如 sum(x) for x... 或 sum(_) for _... 或 sum(readableName) for readableName...),从而获得优雅两次,只是为了迭代。对于 filter 和 reduce 以及来自 itertools 模块的任何东西,同样的论点也成立:如果您已经有一个方便的函数,您可以继续进行一些函数式编程。这在某些情况下获得了可读性,而在其他情况下则失去了可读性(例如,新手程序员,多个参数)......但是您的代码的可读性在很大程度上取决于您的评论。

几乎从不:在进行函数式编程时,您可能希望将 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 循环:您当然也可以仅使用 for 循环。虽然从函数式编程的角度来看并不那么优雅,但有时非局部变量会使命令式编程语言(如 python)中的代码更清晰,因为人们非常习惯以这种方式阅读代码。通常,当您仅执行任何复杂的操作时,for 循环也是最有效的在记忆方面有效(不一定在时间方面,我希望在最坏的情况下是一个恒定因素,除非出现一些罕见的病态垃圾收集打嗝)。

“蟒蛇主义”

我不喜欢“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], ... 的顺序进行评估.

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

% 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”内置函数说这对那些使用它的人来说是不公平的{ 1} 和 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 在它的全部,那么我真的不知道“Pythonic”应该是什么意思,但我认为它不会类似于“GvR 建议人们使用的东西”。
@SteveJessop:我只针对 map/filter,而不是 itertools。函数式编程完全是 Python 风格的(itertoolsfunctoolsoperator 都是专门为函数式编程而设计的,我一直在 Python 中使用函数式惯用语),并且 itertools 提供了令人痛苦的功能为了实现自己,特别是 mapfilter 与生成器表达式的冗余使 Guido 讨厌它们。 itertools 一直都很好。
如果有办法,我可以喜欢这个答案。很好解释。
C
Community

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

即使它们不是“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 超过几个月的人来说,这并不是一个微妙的错误”时,这句话的字面意思是“这只涉及没有经验的开发人员”(显然不是你)。为了记录,您显然没有阅读答案,因为我用粗体表示我正在移动而不是复制代码。跨语言的复制粘贴错误非常统一。由于其作用域,这种错误对于 Python 来说更为独特;它更微妙,更容易忘记和错过。
切换到 map 和/或 filter 仍然不是合乎逻辑的原因。如果有的话,避免您的问题的最直接和合乎逻辑的翻译不是 map(lambda x: x ** 2, numbers),而是不泄漏的生成器表达式 list(x ** 2 for x in numbers),正如 JeromeJ 已经指出的那样。看Mehrdad,不要这么个人地投反对票,我只是强烈不同意你在这里的推理。
r
raek

实际上,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 返回一个可迭代的,而不是一个迭代器。
n
ninjagecko

这是一种可能的情况:

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。
M
Mike McKerns

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


D
Dan

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

还有一个采访在某处(我不能随便找到),Guido 将 lambda 和函数式函数列为他最后悔接受 Python 的事情,所以你可以说它们不是 -凭借这一点,Pythonic。


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

因此,由于 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(map())

你不需要列表对象,你只需要一个可迭代的对象:

始终使用 map()


c
craymichael

我进行了一个快速测试,比较了调用对象方法的三种方法。在这种情况下,时间差可以忽略不计,并且与所讨论的功能有关(请参阅@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 中的一个简单优化,用于定义类(属性)所需的总内存,从而减少内存大小。这是结果图。

https://i.stack.imgur.com/PIm1z.png

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

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


正如其他答案中已经解释的那样,只有以完全相同的方式调用函数(即 [*map(f, vals)][f(x) for x in vals]),map 才会更快。所以 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() 调用增加了开销。应该花更多时间阅读答案。我将重新运行这些测试以进行公平比较,但差异可能可以忽略不计。
M
Mohit Raj

我尝试了@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 是非常标准的。
T
Tanvi Penumudy

https://i.stack.imgur.com/ZPBTx.png

图片来源:Experfy

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

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

希望能帮助到你!祝你好运 :)


目前尚不清楚运行了哪些确切的代码或样本有多大。无论如何,for-loop 和 map 之间的巨大差异似乎是不正确的。
N
Nico Schlömer

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

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

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

hex

https://i.stack.imgur.com/5PGSU.png

x ** 2

https://i.stack.imgur.com/Ujs5b.png

重现绘图的代码:

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

l
lmiguelvargasf

我认为最 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 才能允许过滤。


H
HoD

我的用例:

def sum_items(*args):
    return sum(args)


list_a = [1, 2, 3]
list_b = [1, 2, 3]

list_of_sums = list(map(sum_items,
                        list_a, list_b))
>>> [3, 6, 9]

comprehension = [sum(items) for items in iter(zip(list_a, list_b))]

我发现自己开始使用更多地图,我认为由于传递和返回参数,map 可能比 comp 慢,这就是我找到这篇文章的原因。

我相信使用 map 可以更具可读性和灵活性,尤其是当我需要构造列表的值时。

如果您使用地图,您实际上会在阅读时理解它。

def pair_list_items(*args):
    return args

packed_list = list(map(pair_list_items,
                       lista, *listb, listc.....listn))

加上灵活性奖金。并感谢所有其他答案,以及绩效奖金。