案例
-
常见情况:几乎总是,您会希望在 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 模块中的任何内容都适用相同的论点:如果您已经有一个方便的函数,您可以继续进行一些函数式编程。这在某些情况下获得了可读性,而在其他情况下则失去了可读性(例如,新手程序员、多个参数)......但无论如何,您的代码的可读性很大程度上取决于您的 cmets。
-
几乎从不:在进行函数式编程时,您可能希望将
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-loop:当然你也可以只使用for循环。虽然从函数式编程的角度来看并不那么优雅,但有时非局部变量会使命令式编程语言(如 python)中的代码更清晰,因为人们非常习惯以这种方式阅读代码。通常,当您仅执行任何复杂操作时,for 循环也是最有效的在内存方面效率很高(不一定在时间方面,我希望在最坏的情况下是一个恒定因素,除非出现一些罕见的病态垃圾收集打嗝)。
“Python主义”
我不喜欢“pythonic”这个词,因为我发现pythonic 在我眼中并不总是优雅的。尽管如此,map 和 filter 以及类似的函数(比如非常有用的 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], ... 的顺序进行评估.
但是,假设我们有一个预制函数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 年左右的 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 类对反汇编来说有点不透明,但我们可以通过速度测试来完成。