生成器(generator)

  • 生成器,即生成一个容器。
  • 在Python中,一边循环,一边计算的机制,称为生成器。
  • 生成器可以理解为一种数据类型,这种数据类型自动实现了迭代器协议(其他数据类型需要调用自己的内置iter()方法或__iter__()的内置函数),所以,生成器就是一个可迭代对象。
  • 在Python中,使用生成器可以很方便的支持迭代器协议。

生成器优点

python使用生成器对延迟操作提供了支持,所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。

  • 生成器是可迭代对象;
  • 实现了延迟计算,省内存(按需执行);
  • 生成器本质和其他类型一样,都是实现了迭代器协议,只不过生成器是一边计算,一边生成,从而节省内存空间,其余的可迭代对象可没有这个功能。

生成器分类

  • 生成器函数:生成器函数可以通过常规的def语句定义,不同的是,不是使用return返回,而是用yield一次返回一个结果,在每个结果之间挂起和继承它们的状态,来自动实现迭代协议。
  • 生成器表达式:类似于列表推导,但,生成器返回按需生产结果的一个对象,而不是一次构建一个结果列表。

生成器表达式

  • 生成器表达式:
gen_exp = (i for i in range(10))  #生成器表达式
print(gen_exp) #generator
# for i in gen_exp:  #取出生成器表达式的值,for循环
#     print(i)
print(gen_exp.__next__()) #next方法
print(gen_exp.__next__())
print(gen_exp.__next__())
print(gen_exp.__next__())
print(gen_exp.__next__())
  • 生成器表达式和列表生成式比较:
g = (i for i in range(10**100))#生成器表达式
l = [i for i in range(10**100)]#列表生成式

print g.__next__()#更省内存,需要一个取一个
print l.__next__()#需要在内存中创建1行10**100列的序列
  • 列表解析与生成器表达式都是一种便利的编程方式,生成器表达式使用了”惰性计算”(lazy evaluation),只有在检索时才被赋值(evaluated),所以在列表比较长的情况下使用内存上更有效。
  • python不但使用迭代器协议让for循环更加通用,大部分内置函数,也是使用迭代器协议访问对象的如,sum函数是python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议。
  • 生成器表达式并不是创建一个列表, 而是返回一个生成器,这个生成器在每次计算出一个条目后,把这个条目”产生”(yield)出来。

生成器函数

  • 在Python中,使用了yield的函数就称为生成器。
  • 跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,可以理解为:生成器就是一个迭代器。
  • 生成器和函数的执行流程不一样,函数是顺序执行,遇到return语句或者最后一行函数语句就结束。在调用生成器运行过程中,每次遇到yield时函数会暂停并保存当前所有的运行信息,返回yield值。并在下一次执行next方法时,从当前位置继续运行。
>>> gen = (i for i in range(5))
>>> gen
<generator object <genexpr> at 0x0000004DE29A70A0>
>>> next(gen)
0
>>> next(gen)
1
>>> next(gen)
2
>>> next(gen)
3
>>> next(gen)
4
>>> next(gen)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

注意:generator保存的是算法,每次调用next方法,就计算出gen的下一个元素的值,直到计算到最后一个元素,没有更多元素时,就StopIteration的错误。
当然,上面这种不断调用next(gen),用着有点坑,正确的方法是使用for循环,因为generator也是iterator:

>>> g = (i for i in range(5))
>>> for i in g:
...     print(i)
...
0
1
2
3
4

小例子:

def read_file(fpath): 
    BLOCK_SIZE = 1024 
    with open(fpath, 'rb') as f: 
        while True: 
            block = f.read(BLOCK_SIZE) 
            if block: 
                yield block 
            else: 
                return

如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐