>>> from env_helper import info; info()
页面更新时间: 2022-03-23 10:13:35
运行环境:
    Linux发行版本: Debian GNU/Linux 11 (bullseye)
    操作系统内核: Linux-5.10.0-11-amd64-x86_64-with-glibc2.31
    Python版本: 3.9.2

13.2. 列表推导式,生成器与迭代器

13.2.1. 列表推导式

列表推导式是颇具python风格的一种写法。这种写法除了高效,也更简短。

>>> {i:el for i,el in enumerate(["one","two","three"])}
{0: 'one', 1: 'two', 2: 'three'}

enumerate() 是内建函数,可以让列表获得“下标”的属性。 而如果不用列表推导式,上例需要这么写:

>>> lst = ["one","two","three"]
>>> i = 0
>>> for e in lst:
>>>     lst[i] = '%d: %s' % (i,lst[i])
>>>     i +=1
>>> lst
['0: one', '1: two', '2: three']

13.2.2. 迭代器

迭代器属于一个临时区,安排一些元素在里面,但只用用的时候才会创建一些临时区,一旦遍历结束则临时区清空,再遍历就失效了。所以说迭代器能够减少内存的开销。

下面用代码来说明这句话的意思。

>>> import sys
>>> i = iter(range(10000))
>>> id(i.__next__())
140412289595664
>>> sys.getsizeof(i)
48
>>> sys.getsizeof(i.__next__())
28
>>> e = range(10000)
>>> sys.getsizeof(e)
48
>>> sys.getsizeof(list(e))
80056

可以看到,如果一次性把list全部加载进来,需要90112byte内存空间,如果使用迭代器迭代,仅仅需要28byte内存空间。

可以被 next() 函数调用并不断返回下一个值的对象称为迭代器。 生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator, list,tuple,dict,str是有可迭代属性(惰性循环),但如果需要转化成可迭代对象, 可以用 iter() 来转换,验证方式就是看对象是否有__next__方法。

13.2.3. 生成器(generator)

生成器是一种特殊的迭代器。

什么时候需要用生成器

其实一般情况下是不需要生成器的,只有当因为性能限制下才需要用到,比如你需要用python来read一个10g的txt文件,如果一次性把10g的文件加载到内存再处理(.read()方法),内存肯定溢出了。这里如果使用生成器,就可以把读和处理交叉进行,比如使用(.readline和.readlines)就可以在循环读取的同时不断处理,这样可以节省大量内存空间。此外,还可以使用生成器生成线程池。

(如果当自己写一个读写函数封装给别人使用时,那么要考虑到文件容量问题,此时应该考虑使用生成器。)

生成生成器的两种方法

第一种比较简单,将列表推导式的 [] 改称 () 就可以了:

>>> g = (x*x for x in range(10))

第二种办法就是在函数里加入 yield 关键字。 yieldreturn 有点类似,都可以用来返回值, 不同的是 yield 遇到 next() 就返回,再次执行时从上次返回的 yield 语句处继续执行。

如何判断一个函数是否是生成器

判断生成器的办法就是查看其属性

>>> '__next__' in dir(g)
True

在这里可以看到g有一个__next__的魔术方法,而这是生成器所特有的属性,下面两种方式调用都可以。

>>> g.__next__()
0
>>> g.__next__()
1
>>> next(g)
4