有什么理由比列表理解更喜欢使用map()
,反之亦然? 它们中的一个通常比另一个效率更高,或者通常被认为比另一个更Python化吗?
#1楼
在某些情况下, map
在微观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
#2楼
我发现列表理解通常比map
更能表达我要执行的操作-它们都可以完成操作,但是前者节省了试图了解什么可能是复杂的lambda
表达式的精神负担。
在某个地方(我无法找到它)也有一次采访,其中Guido列出了lambda
和函数功能,这是他最后悔接受Python的事情,因此您可以通过以下方式来论证它们是非Python的:的优点。
#3楼
Python 2:您应该使用map
和filter
而不是列表推导。
即使它们不是“ 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
不在同一范围内。
直到我将内部块移到代码的不同部分之后,问题才出现(阅读:维护期间的问题,而不是开发过程中的问题),而且我没想到。
是的, 如果您从未犯过此错误,则列表理解会更优雅。
但是从个人经验(和其他人看到的相同的错误)来看,我已经看到它发生了很多次,以至于当这些错误潜入您的代码中时,您不应该经历这种痛苦。
结论:
使用map
和filter
。 它们可以防止与范围相关的细微难以诊断的错误。
边注:
如果适合您的情况,不要忘记考虑使用imap
和ifilter
(在itertools
)!
#4楼
这是一种可能的情况:
map(lambda op1,op2: op1*op2, list1, list2)
与:
[op1*op2 for op1,op2 in zip(list1,list2)]
我猜想zip()是一个不幸的和不必要的开销,如果您坚持使用列表推导而不是地图,则需要沉迷于此。 如果有人肯定或否定这一点,那就太好了。
#5楼
实际上, map
和list的理解在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(和Python 2中的imap
中map
的返回值不是列表,而是一个迭代器!
与遍历列表不同,遍历迭代器时将消耗元素。 这就是为什么squares
在最后一个print(list(squares))
行中看起来是空的。
总结一下:
- 在处理迭代器时,必须记住它们是有状态的,并且在遍历它们时会发生变化。
- 列表更容易预测,因为它们仅在您显式对其进行更改时才会更改; 他们是容器 。
- 还有一个好处:数字,字符串和元组甚至可以更容易预测,因为它们根本无法更改; 它们是价值 。
#6楼
如果您打算编写任何异步,并行或分布式代码,则您可能会更喜欢map
而不是列表理解-因为大多数异步,并行或分布式程序包都提供了map
函数来重载python的map
。 然后,通过将适当的map
函数传递给其余代码,您可能不必修改原始串行代码即可使其并行运行(等)。
#7楼
因此,由于Python 3的map()
是一个迭代器,因此您需要牢记所需的东西:一个迭代器或list
对象。
正如@AlexMartelli所提到的 ,仅当您不使用lambda
函数时, map()
才比列表理解要快。
我将向您介绍一些时间比较。
Python 3.5.2和CPython
我用过木星笔记本 ,尤其是%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()
!
#8楼
我认为最Python的方式是使用列表理解而不是map
和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
如您所见,理解不需要map
所需的额外lambda
表达式。 此外,理解还允许轻松过滤,而map
需要filter
才能过滤。
#9楼
我进行了一项快速测试,比较了三种调用对象方法的方法。 在这种情况下,时差可以忽略不计,并且与所讨论的功能有关(请参阅@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]
我查看了整数(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
( map_comprehension
技术)对于对象中两种类型的加法最快,尤其是对于较短的列表。
访问此pastebin以获取用于生成图和数据的源。
#10楼
案例
- 常见情况 :几乎总是,您将要在python中使用列表推导,因为对于新手程序员来说,阅读代码会更加明显。 (这不适用于可能适用其他习惯用法的其他语言。)由于列表推导是python中用于迭代的事实上的标准,因此您对python程序员所做的工作甚至会更加明显。 他们是预期的 。
- 较少见的情况 :但是,如果已经定义了函数 ,则使用
map
通常是合理的,尽管它被认为是“非pythonic”的。 例如,map(sum, myLists)
比[sum(x) for x in myLists]
更为优雅/简洁。 您可以不必键入一个虚拟变量(例如sum(x) for x...
或sum(_) for _...
或sum(readableName) for readableName...
),从而获得优雅的sum(_) for _...
两次,只是要迭代。 相同的参数适用于filter
和reduce
以及itertools
模块中的任何内容:如果您已经有了函数,可以继续进行一些函数式编程。 在某些情况下,这会提高可读性,而在其他情况下(例如,新手程序员,多个参数),则会失去可读性。但是,无论如何,代码的可读性在很大程度上取决于注释。 - 几乎永远不会 :在执行函数编程时,您可能想将
map
函数用作纯抽象函数,在此情况下,您正在映射map
或curryingmap
,或者从谈论map
作为函数中受益。 例如,在Haskell中,称为fmap
的函子接口可对任何数据结构进行通用映射。 这在python中非常罕见,因为python语法迫使您使用生成器样式来谈论迭代; 您不能轻易将其概括。 (这有时是好事,有时是坏事。)您可能会想出一些罕见的python示例,其中map(f, *lists)
是合理的做法。 我能想到的最接近的示例是sumEach = partial(map,sum)
,它是一种单行代码,大致相当于:
def sumEach(myLists):
return [sum(_) for _ in myLists]
- 仅使用
for
-loop :当然,您也可以只使用for
-loop 。 从功能编程的角度来看,虽然不是那么优雅,但有时非局部变量使命令式编程语言(例如python)中的代码更清晰,因为人们已经非常习惯以这种方式阅读代码。 通常,当您仅执行任何不构建列表的复杂操作(例如列表理解和映射)(例如,求和或制作树等)时,for循环也是最有效的。就内存而言,它是高效的(不必在时间上,我希望在最坏的情况下,它是一个恒定的因素,除非出现一些罕见的病理性垃圾收集问题)。
“ Python主义”
我不喜欢“ pythonic”这个词,因为我发现pythonic在我眼中并不总是那么优雅。 但是,就样式而言, map
和filter
以及类似的功能(例如非常有用的itertools
模块)可能被认为是非Python的。
懒惰
就效率而言,就像大多数函数式编程构造一样, 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(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...
输入[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], ...
的顺序求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是在带有python 3。?。?的大约2010年英特尔工作站上执行的,而B和C是在带有python 3.2.1的大约2013年AMD工作站上执行的,具有截然不同的硬件。 结果似乎是,地图和列表理解的性能可比,这受其他随机因素的影响最大。 我们可以告诉的唯一的事情似乎是,奇怪的是,虽然我们期待list解析[...]
除发电机表达式执行好(...)
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
类在反汇编方面有点不透明,但是我们可以通过速度测试来确定。