Python 的高级特性:列表推导式、生成器与迭代器

file

列表推导式

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

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

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

In [24]: lst = ["one","two","three"]

In [25]: i = 0

In [26]: for e in lst:
   ....:     lst[i] = '%d: %s' % (i,lst[i])
   ....:     i +=1
   ....:   

In [27]: lst
Out [27]: ['0: one', '1: two', '2: three']

迭代器

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

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

In [29]: import sys

In [30]: i = iter(range(10000))

In [32]: id(i.__next__())
Out[32]: 4388723488

In [33]: sys.getsizeof(i)
Out[33]: 48

In [34]: sys.getsizeof(i.__next__())
Out[34]: 28

In [35]: e = range(10000)

In [37]: sys.getsizeof(e)
Out[37]: 48

In [38]: sys.getsizeof(list(e))
Out[38]: 90112

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

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

生成器「generator」

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

什么时候需要用生成器

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

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

生成生成器的两种方法

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

In [40]: g = (x*x for x in range(10))

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

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

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

In [41]: dir(g)
Out[41]: 
['__class__',
...
 '__next__',
...
 '__repr__',
 '__setattr__',
]

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

In [42]: g.__next__()
Out[42]: 0

In [43]: g.__next__()
Out[43]: 1

In [44]: next(g)
Out[44]: 4

参考

PS

感谢关注「GitHub 热门」公众号,带你了解技术圈内热门新鲜事!

file

本作品采用《CC 协议》,转载必须注明作者和本文链接
感谢关注「GitHub 热门」公众号
Destiny
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!