列表理解与地图
是否有理由更喜欢使用列表理解,反之亦然?它们中的任何一个通常比另一个更有效率还是被认为通常更pythonic?map()
是否有理由更喜欢使用列表理解,反之亦然?它们中的任何一个通常比另一个更有效率还是被认为通常更pythonic?map()
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
当映射需要 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
例
map
map(sum, myLists)
[sum(x) for x in myLists]
sum(x) for x...
sum(_) for _...
sum(readableName) for readableName...
filter
reduce
itertools
map
map
map
map
fmap
map(f, *lists)
sumEach = partial(map,sum)
def sumEach(myLists):
return [sum(_) for _ in myLists]
for
循环:当然,您也可以只使用 for 循环。虽然从函数式编程的角度来看并不那么优雅,但有时非局部变量会使命令式编程语言(如python)中的代码更清晰,因为人们非常习惯于以这种方式阅读代码。一般来说,当你只是做任何复杂的操作时,For循环也是最有效的,这些操作没有构建列表,比如列表推导和map被优化(例如求和,或做一棵树等) - 至少在内存方面是有效的(不一定是在时间方面,我最坏的情况是期望一个常数因子, 除了一些罕见的病理性垃圾收集打嗝)。“Pythonism”
我不喜欢“pythonic”这个词,因为我发现pythonic在我眼中并不总是优雅的。然而,和类似的函数(如非常有用的模块)在风格方面可能被认为是非Pythonic的。map
filter
itertools
懒惰
在效率方面,像大多数函数式编程结构一样,MAP可以是懒惰的,实际上在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中的生成器表达式)将避免计算它们的值,直到最后一刻需要。通常,这通常会超过使用地图
的任何开销。缺点是,与大多数函数式语言相比,这在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年使用python 3.?.?,的Intel工作站上执行的,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