Python列表函数或列表推导式

17

有人能否解释一下这两种创建列表的方式之间的区别? 它们是同样的东西吗? 如果不是,我应该使用哪一个?

squares1 = [x**2 for x in range(1, 11)]
squares2 = list(x**2 for x in range(1, 11))

为什么这很重要呢?虽然99%的情况下这些微小的差别并不重要。一般来说,我更喜欢使用list(map(math.pow, range(1, 11), repeat(2))),但这主要是个人口味问题。 - Reut Sharabani
我认为第一个是列表推导式,而第二个是 Python 提供的 lambda 表达式的一部分。我相信大多数人喜欢列表推导式而不是 lambda,但我无法在这里提供任何技术细节。 - The Fool
1
列表推导式不会预先分配完全所需的内存。它们基本上像带有附加操作的for循环一样工作,这会过度分配内存。 - juanpa.arrivillaga
2
@TheFoom 第二个 o 与 lambda 没有关系,它是一个生成器表达式,会被实例化为一个列表。 - juanpa.arrivillaga
@juanpa.arrivillaga 哦,谢谢 :) 我又学到了一点。所以我猜就像下面的真正答案一样,主要区别在于懒惰与渴望? - The Fool
显示剩余3条评论
4个回答

14

从下面可以看出,它们的性能略有不同:

squares1 = [x**2 for x in range(1, 11)]

每次循环平均需要 3.07 微秒,标准差为 70 纳秒 (7 次运行,每次循环执行 100000 次)

squares2 = list(x**2 for x in range(1, 11))

每次循环的平均值为3.65微秒,标准差为35.6纳秒(7次运行,每次循环100000次)

这可能主要是因为在case 1中同时迭代和生成列表值。

在case 2中,您会在迭代过程中生成值,然后在结束时将其转换为列表,并将其存储为给定变量。


3

我认为,第一种方法是直接通过列表推导式将squares1初始化为列表。

另一种方法首先创建生成器类对象,然后将其转换为列表。我认为第一种方法更有效率。

虽然列表和生成器之间有一些小差异,但就我的知识和经验而言,列表可以更快地完成工作,而生成器则可以惰性地产生单个结果以供每次迭代使用。对于大多数任务,我建议选择列表。


2
第一种方法更快。这是因为当它们被编译成字节码时,第一种方法变成了
0  LOAD_CONST               0 (<code object <listcomp> at 0x7fc95aea9ed0, file "<dis>", line 1>)
2  LOAD_CONST               1 ('<listcomp>')
4  MAKE_FUNCTION            0
6  LOAD_NAME                0 (range)
8  LOAD_CONST               2 (1)
10 LOAD_CONST               3 (11)
12 CALL_FUNCTION            2
14 GET_ITER
16 CALL_FUNCTION            1
18 RETURN_VALUE

第二个变成

0  LOAD_NAME                0 (list)
2  LOAD_CONST               0 (<code object <genexpr> at 0x7fc95aea9ed0, file "<dis>", line 1>)
4  LOAD_CONST               1 ('<genexpr>')
6  MAKE_FUNCTION            0
8  LOAD_NAME                1 (range)
10 LOAD_CONST               2 (1)
12 LOAD_CONST               3 (11)
14 CALL_FUNCTION            2
16 GET_ITER
18 CALL_FUNCTION            1
20 CALL_FUNCTION            1
22 RETURN_VALUE

这意味着第二种方法需要多两个指令,使其变慢,即使只是微小的变化。

在我的笔记本电脑上,第一种方法的一百万次迭代需要4.651秒,而第二种方法的一百万次迭代需要5.483秒。


这两个指令并不是导致它变慢的原因。它们只会在迭代中出现一次,而不是每次迭代都出现。减速的原因是在一般情况下,对生成器进行迭代是很慢的,并且您每个项目都有两个调用next的呼叫,最终进入您的列表。 - juanpa.arrivillaga

2
在第一个例子中,你使用了列表推导式的语法。这是在Python中将某个函数映射到列表的最快方法。
而在第二个例子中,你创建了一个生成器。generator
(x**2 for x in range(1000))

将其立即发送到list()函数。在您的情况下几乎没有区别,因为您获得相同的列表并且执行时间几乎相同。
%%timeit
[i**2 for i in range(1000)]

Out:
170 µs ± 774 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

%%timeit
list(i**2 for i in range(1000))

Out:
187 µs ± 2.45 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

但我不确定两种情况下的内存使用情况-看起来它们是一样的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接