概述

Python中的列表推倒式(List Comprehension) 和 生成器表达式(Generator Expression)是两种很相似的表达式,但意义却不大不同,这儿做一个比照。

列表推导式

列表推导式是比较常用的技能,能将原本需求for loop 和 if else 句子的情况简化成一条指令,最终得到一个列表对象:

even = [e for e in range(10) if e % 2 == 0]

具体细节不过多展开,相信许多运用Python的人都现已足够了解这种语法了。

需求注意的一点是,列表推导式不是慵懒核算 ( Lazy Loading) 的,因而一切的列表成员都在声明完句子后当即核算 (Eager Loading),因而在数组成员许多的情况下,速度会很慢,例如下面的在IPython环境里边的三个列表推导式的耗时计算:

In [1]: %timeit even = [e for e in range(100000) if e % 2 == 0]
5.5 ms  24.8 s per loop (mean  std. dev. of 7 runs, 100 loops each)
In [2]: %timeit even = [e for e in range(1000000) if e % 2 == 0]
58.9 ms  440 s per loop (mean  std. dev. of 7 runs, 10 loops each)
In [3]: %timeit even = [e for e in range(100000000) if e % 2 == 0]
5.65 s  26.5 ms per loop (mean  std. dev. of 7 runs, 1 loop each)

能够看到随着元素个数的增加,列表推导式执行的时刻也相应变长,占用的内存也会变大。

有一种情况是,咱们界说了许多许多的数组元素,但是最终并不是一切的元素都能用到,例如通过几条命令,最终或许只要列表里边的前10个元素会用到,或许只要契合某些条件的元素会用到,这样的话,Eager形式就白白花费了时刻,白白花费了内存来创立许多用不到的元素,这显然有很大的改进空间。

生成器表达式

生成器能表达式处理上面的问题,它的元素迭代是慵懒的,因而只要需求的时候才出产出来,避免了额定的内存开支和时刻开支: 生成器表达式不论元素数目多大,创立时都是常数时刻,由于它并没有当即创立元素。

那么生成器表达式的语法是怎么样的呢,很简单,只需求把列表推导式中的方括号改为圆括号:

even_gen = (e for e in range(10) if e % 2 == 0)

注意它的类型是生成器类型:

type(even_gen)
# generator

创立生成器表达式的耗时计算:

In [1]: %timeit even_gen = (e for e in range(100000) if e % 2 == 0)
376 ns  2.61 ns per loop (mean  std. dev. of 7 runs, 1,000,000 loops each)
In [2]: %timeit even_gen = (e for e in range(10000000) if e % 2 == 0)
382 ns  1.63 ns per loop (mean  std. dev. of 7 runs, 1,000,000 loops each)
In [3]: %timeit even_gen = (e for e in range(1000000000) if e % 2 == 0)
384 ns  2.85 ns per loop (mean  std. dev. of 7 runs, 1,000,000 loops each)

能够看到随着元素的增加,创立时刻基本不变,并且比列表推导式的耗时要低不少。

运用场景选择

那么是不是就是说运用中能够用生成器表达式替代列表推导式了呢,也不尽然,由于列表推导式得到的是一个列表,许多便捷操作(如slice等)能够效果到上面,而生成器表达式则不可:

In [17]: even = [e for e in range(10) if e % 2 == 0]
In [18]: even[:3]
Out[18]: [0, 2, 4]
In [19]: even_gen = (e for e in range(10) if e % 2 == 0)
In [20]: even_gen[:3]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [20], in <cell line: 1>()
----> 1 even_gen[:3]
TypeError: 'generator' object is not subscriptable

并且两者有一个致命的差异:生成器表达式只能迭代一次,而列表推导式能够运用许屡次,举例如下:

In [22]: even_gen = (e for e in range(10) if e % 2 == 0)
In [23]: for e in even_gen:
    ...:     print(e)
    ...:
2
4
6
8
In [24]: for e in even_gen:
    ...:     print(e)
    ...:

能够看到生成器表达式在第2次迭代的时候,里边现已没有元素了!即第一次迭代现已全部生成出来了,而列表推导式是每次迭代都是有相同的内容:

In [25]: even = [e for e in range(10) if e % 2 == 0]
In [26]: for e in even:
    ...:     print(e)
    ...:
2
4
6
8
In [27]: for e in even:
    ...:     print(e)
    ...:
2
4
6
8

因而总结来说,运用主张如下:

  • 假如要屡次迭代时,主张运用列表推导式
  • 假如数组很大或许有无穷个元素,主张运用生成器表达式
  • 其他场景:两者均可,自己看情况运用一个,假如没有速度和方便度的问题即可,假如有问题换另一个再试试

参考

  1. stackoverflow.com/questions/4…
  2. docs.python.org/3/howto/fun…