List comprehension vs map
是否有理由更喜欢使用
在某些情况下,
使用完全相同的功能时地图的微小速度优势示例:
1 2 3 4 | $ 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时,性能比较如何完全颠倒的示例:
1 2 3 4 | $ 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 |
病例好的。
- 常见情况:几乎总是,您会希望在Python中使用一个列表理解,因为您对初学者阅读代码所做的事情会更加明显。(这不适用于其他语言,其他习语也可能适用。)您对Python程序员所做的事情将更加明显,因为列表理解是Python中用于迭代的事实标准;它们是预期的。
- 不太常见的情况:但是,如果您已经定义了一个函数,那么使用
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 作为函数的讨论中获益。例如,在haskell中,一个名为fmap 的函数接口将映射推广到任何数据结构上。这在Python中很少见,因为Python语法强制您使用生成器样式来讨论迭代;您不能轻易地概括它。(这有时是好的,有时是坏的)您可能会想出一些罕见的python示例,其中map(f, *lists) 是一个合理的操作。我能想到的最接近的例子是sumEach = partial(map,sum) ,它是一个单衬层,大致相当于:
好的。
1 2 | def sumEach(myLists): return [sum(_) for _ in myLists] |
- 只使用一个
for 循环:当然也可以使用for循环。虽然从函数式编程的角度看没有那么优雅,但有时非局部变量在诸如python之类的命令式编程语言中使代码更清晰,因为人们非常习惯这样读取代码。对于循环,一般来说,当你仅仅是做一些复杂的操作,而不是构建一个像列表这样的列表理解和映射时,它是最有效的(例如求和,或者生成一棵树等等),至少在内存方面是最有效的(不一定是在时间方面,在最坏的情况下,我期望一个常量因子,除了一些罕见的病理性垃圾收集打嗝)。
"巨蛇座"好的。
我不喜欢"Python"这个词,因为我不觉得我眼中的Python总是那么优雅。然而,从风格上来说,
懒惰好的。
在效率方面,和大多数函数式编程构造一样,map可能是懒惰的,实际上在python中也是懒惰的。这意味着您可以这样做(在python3中),并且您的计算机不会耗尽内存并丢失所有未保存的数据:好的。
1 2 | >>> map(str, range(10**100)) <map object at 0x2201d50> |
试着用一个清单来理解:好的。
1 2 | >>> [str(n) for n in range(10**100)] # DO NOT TRY THIS AT HOME OR YOU WILL BE SAD # |
请注意,列表理解本身也是懒惰的,但Python选择将其实现为非懒惰的。然而,python支持生成器表达式形式的惰性列表理解,如下所示:好的。
1 2 | >>> (str(n) for n in range(10**100)) <generator object <genexpr> at 0xacbdef> |
基本上可以将
简单的人为例子好的。
1 2 3 4 5 6 | 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))}) |
列表理解是非惰性的,因此可能需要更多的内存(除非使用生成器理解)。方括号
1 2 3 | print( {x:x**2 for x in (-y for y in range(5))} ) |
或者打破现状:好的。
1 2 3 4 | rangeNeg5 = (-y for y in range(5)) print( {x:x**2 for x in rangeNeg5} ) |
python3的效率比较好的。
1 2 | % 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中的
但是,假设我们有一个预先设定的函数
1 2 3 4 5 6 7 8 9 10 11 | % 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>) |
结果以a a a/bbb/ccc的形式出现,其中a是在Circa-2010 Intel工作站上用python 3执行的。?,B和C使用Circa-2013 AMD工作站和python 3.2.1执行,硬件非常不同。其结果似乎是地图和列表理解在性能上具有可比性,这是受其他随机因素影响最大的。我们唯一能说的似乎是,奇怪的是,虽然我们期望清单理解
重要的是要认识到,这些测试假定了一个非常简单的函数(标识函数);但是这是很好的,因为如果函数很复杂,那么与程序中的其他因素相比,性能开销可以忽略不计。(用其他简单的东西如
如果您擅长阅读python程序集,那么可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | >>> 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 |
nbsp;好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | >>> 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 |
nbsp;好的。
1 2 3 4 5 6 7 8 9 | >>> 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 |
似乎使用
你应该使用
即使他们不是"Python"你也应该喜欢他们的一个客观原因是:它们需要函数/lambda作为参数,这将引入一个新的范围。
我被这个咬了不止一次:
1 2 3 4 | for x, y in somePoints: # (several lines of code here) squared = [x ** 2 for x in numbers] # Oops, x was silently overwritten! |
但如果我说:
1 2 3 | for x, y in somePoints: # (several lines of code here) squared = map(lambda x: x ** 2, numbers) |
那么一切都会好起来的。
你可以说我在同一个范围内使用相同的变量名是愚蠢的。
我没有。代码本来很好——两个
是的,如果你从未犯过这个错误,那么列表理解就更优雅了。但是从个人经验(以及从看到别人犯同样的错误)来看,我已经看到这种情况发生了很多次,以至于我认为当这些bug蔓延到代码中时,你所经历的痛苦是不值得的。
结论:使用
如果适合您的情况,不要忘记考虑使用
实际上,在python 3语言中,
1 2 3 4 5 | def square(x): return x*x squares = map(square, [1, 2, 3]) print(list(squares)) print(list(squares)) |
您可能期望它打印两次行"[1,4,9]",但它打印的是"[1,4,9]",后面跟着"[]"。第一次看
在python 2语言中,
元素在迭代迭代器时使用,与在列表上迭代时不同。这就是为什么
总结:
- 在处理迭代器时,必须记住它们是有状态的,并且在遍历它们时它们会发生变化。
- 列表更容易预测,因为它们只在您显式地改变它们时发生变化;它们是容器。
- 还有一个好处:数字、字符串和元组更容易预测,因为它们根本不能改变;它们是值。
如果您计划编写任何异步、并行或分布式代码,那么您可能更喜欢使用
我发现列表理解通常比
还有一个采访在那里(我不能马上找到),在那里guido列出了
这里有一个可能的例子:
1 | map(lambda op1,op2: op1*op2, list1, list2) |
对比:
1 | [op1*op2 for op1,op2 in zip(list1,list2)] |
我猜zip()是一种不幸的、不必要的开销,如果您坚持使用列表理解而不是地图,那么您需要投入其中。如果有人能肯定地或否定地澄清这一点,那就太好了。
因此,由于python 3,
正如@alexmartelli已经提到的,只有在不使用
我会给你一些时间比较。
设置:
1 2 | x_list = [(i, i+1, i+2, i*2, i-9) for i in range(1000)] i_list = list(range(1000)) |
内置功能:
1 2 3 4 5 6 7 8 9 10 | %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 |
1 2 3 4 5 6 7 8 9 10 | %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。所以我认为把它加入比较是有用的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | %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 |
您需要
如果是自定义函数,则使用列表理解;如果有内置函数,则使用
务必使用
我认为最偏执的方法是使用列表理解,而不是使用
1 2 3 4 5 6 | 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 |
正如你所看到的,理解不需要额外的