是否有理由更喜欢使用 map()
而不是列表理解,反之亦然?它们中的任何一个通常比另一个更有效还是被认为更像蟒蛇?
原文由 TimothyAWiseman 发布,翻译遵循 CC BY-SA 4.0 许可协议
是否有理由更喜欢使用 map()
而不是列表理解,反之亦然?它们中的任何一个通常比另一个更有效还是被认为更像蟒蛇?
原文由 TimothyAWiseman 发布,翻译遵循 CC BY-SA 4.0 许可协议
案例
map
通常是合理的,尽管它被认为是“非 pythonic”。例如, 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
,38-7或者以其他方式从谈论 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 循环也是最有效的,例如列表推导和地图优化(例如求和或制作树等) - 至少在内存方面高效(不一定在时间方面,我希望在最坏的情况下是一个常数因素,除非出现一些罕见的病态垃圾收集打嗝)。“蟒蛇主义”
我不喜欢“pythonic”这个词,因为我不觉得pythonic在我眼里总是优雅的。然而, map
和 filter
和类似的功能(比如非常有用的 itertools
模块)可能被认为是非Python风格的。
懒惰
在效率方面,像大多数函数式编程构造一样, 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 中的生成器表达式)将避免计算它们的值,直到最后一次必要的时刻。通常这通常会超过使用 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 年的英特尔工作站上使用 python 3.?.? 执行的,B 和 C 是在大约 2013 年的 AMD 工作站上使用 python 3.2.1 执行的,具有截然不同的硬件。结果似乎是 map 和 list comprehension 在性能上具有可比性,这受其他随机因素的影响最大。我们唯一可以说的似乎是,奇怪的是,虽然我们期望列表 [...]
比生成器表达式 (...)
, 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
类对于反汇编来说有点不透明,但我们可以通过速度测试来弥补。
原文由 ninjagecko 发布,翻译遵循 CC BY-SA 4.0 许可协议
2 回答5.2k 阅读✓ 已解决
2 回答1.1k 阅读✓ 已解决
4 回答1.4k 阅读✓ 已解决
3 回答1.3k 阅读✓ 已解决
3 回答1.2k 阅读✓ 已解决
2 回答862 阅读✓ 已解决
1 回答1.7k 阅读✓ 已解决
map
在某些情况下可能在微观上更快(当您不是为此目的制作 lambda,而是在 map 和 listcomp 中使用相同的函数时)。在其他情况下,列表推导式可能更快,而且大多数(不是全部)pythonist 认为它们更直接、更清晰。使用完全相同的功能时 map 的微小速度优势的示例:
当 map 需要 lambda 时,性能比较如何完全逆转的示例: