TLDR: comprehension 在每次评估其整个表达式。map 只在第一次评估表达式,并在之后的每次使用结果。
map
和 comprehension 处理传入的表达式方式不同。
可以将 map
和 comprehension 的关键部分翻译成如下方式:
def map(func: 'callable', iterable):
for item in iterable:
yield func(item)
def comprehension(expr: 'code', iterable):
for item in iterable:
yield eval(expr)
重要的区别是
map
已经接收了一个
函数,而
comprehension
接收一个
表达式。
现在,
lambda x:x
是一个
求值为函数的表达式。
>>> co = compile('lambda x: x', '<stackoverflow>', 'eval')
>>> co
<code object <module> at 0x105d1c810, file "<stackoverflow>", line 1>
>>> eval(co)
<function __main__.<lambda>(x)>
值得注意的是,评估表达式以获取函数是需要时间的操作。
由于map
和推导式期望不同的内容,因此lambda
被以不同的方式传递给它们。解释器所做的可以通过显式的eval
/compile
进行扩展:
>>> list(map(eval(compile('lambda x: x', '<stackoverflow>', 'eval')), range(10)))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(comprehension(compile('(lambda x: x)(item)', '<stackoverflow>', 'eval'), range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
重要的区别现在应该清楚了:
- 对于 `map`,表达式在传递到 `map` 之前只被计算一次。对于每个项目,`map` 调用生成的函数。
- 对于推导式,表达式未经计算即被传递。对于每个项目,推导式构造函数,然后调用生成的函数。
需要注意的是,`map` 并不总是比推导式更快。`map` 的好处在于只需创建/查找一次函数。但是,函数调用很费资源,而且推导式不必使用函数。
通常情况下,如果表达式仅使用推导式内部的本地变量,则速度会更快。
In [302]: %timeit list(map(lambda i:i,range(100000)))
...: %timeit [i for i in range(100000)]
8.86 ms ± 38.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
4.49 ms ± 35.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
如果表达式本身必须动态执行一项昂贵的操作,则
map
无法产生好处。
In [302]: %timeit list(map(lambda i: (lambda x:x)(i),range(100000)))
...: %timeit [(lambda x:x)(i) for i in range(100000)]
19.7 ms ± 39.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
15.9 ms ± 43.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)