生成器与迭代器

迭代器与生成器

主要内容:

  • 迭代器的介绍
  • 迭代器的取值
  • for循环的原理
  • 生成器
  • 生成器传值

 

1. 迭代器介绍

  迭代的概念是循环的执行某一过程,且后一次执行会用到前一次的结果,在Python中的迭代,就一般是指利用for循环迭代取值。Python中支持迭代的基本数据类型主要有字符串,列表,元组等,这些都可以算是可迭代对象,那么什么是可迭代对象,什么又是迭代器呢?

  可迭代对象可以理解为内置有__iter__方法的对象,它返回的是一个迭代器,迭代器就是一个实现了__iter__方法和__next__的方法的对象,简而意之,可迭代对象未必是迭代器,迭代器一定是可迭代对象。例如我们通用的列表等数据类型就只是一个可迭代对象,而利用open方法产生的文件对象就是一个迭代器。

2. 迭代器的取值

  我们在利用for循环取值的时候,其实解释器内部调用的其实是可迭代对象的__iter__方法,如下所示,遍历列表的过程

l = [1, 2, 3, 4]
for i in l.__iter__():
    print(i)

print(l.__iter__())
1
2
3
4
<list_iterator object at 0x0000020EA8017CC0>

  list对象被for循环时生成了一个list_iterator,然后依次迭代输出,那迭代器是如何取值的呢?迭代器是通过每次调用它的__next__方法,直到迭代器的值被取完后,抛出StopIteration异常为止。

  迭代器有几个特殊的特点,迭代器本身无论调用多少次__iter__方法,得到的都是它自己,此外迭代器迭代的过程只能一路向前,不能回退取值。

3. for循环的原理

  了解了__iter__方法和__next__的方法,其实就已经了解了for循环的本质了,for循环其实就是内部帮我们先获得可迭代对象的__iter__方法,获得迭代器, 然后不断调用迭代器的__next__方法,值取完后,最后通过捕获StopIteration异常,防止程序异常。实现的方法如下:

l = [1, 2, 3, 4]

l_iter = l.__iter__()
while True:
    try:
        print(l_iter.__next__(), end=' ')
    except StopIteration:
        break
1 2 3 4 

 

4. 生成器

  生成器就是一种一次只能产生一个值的特殊函数,它本质上就是一个用户自定义的迭代器。使用好生成器可以在处理一些大文件的时候大大节省内存,因为取一个数才生成一个。在Python中定义生成器主要有两种方式第一种是函数体中存在yield关键字,第二种是利用生成器表达式。

def gen():
for i in range(1, 4):
yield i

gen2 = (i for i in range(2, 5))
print(gen, gen2)
<function gen at 0x0000029AA9532E18> <generator object <genexpr> at 0x0000029AB890F990>

  由上面可知生成器比较特殊的地方在于它在利用函数定义完之后,它还是一个函数,只有像普通函数一样调用后,它才会由函数转变为生成器。之后我们可以把它当做一个迭代器,每一次利用__next__取值时,生成器就会运行到下一个yield表达式处,每一次产出的值就是yield后面的值,此处可以同时产出多个值,会以元组的形式返回。生成器像函数一样运行完毕后,会像普通迭代器一样,抛出StopIteration异常,所以说生成器本质上就是一个迭代器。

5. 生成器传值

  生成器另一个特殊的地方在于可以向它传值,可以利用send函数向生成器的第一处yield处传值,为什么这么说呢?因为send传的值,生成器是通过yield左边的变量接收的,这只有当生成器启动之后,且运行到第一处后,才生效。不然生成器刚定义完毕,就调用send方法传入非None值就会报错。

def gen():
    print('1')
    recv = yield 5
    print('2')
    print(recv)
    yield


g = gen()
next(g)
g.send(4)
1
2
4

  从这个例子看出来生成器还可以通过send方法取值,send方法也会促使生成器向前运行。

6. 生成器面试题

def add(n, i):
    return n + i


def test():
    for i in range(4):
        yield i


g = test()

for n in [1, 10]:
    g = (add(n, i) for i in g)

print(n)
res = list(g)

  分析一下这个面试题,需要牢记生成器的核心,生成器定义后只是在一处内存空间存储了代码,并没有真正执行。此处其实产生了3个生成器,

就是最后的g = (add(n, i) for i in (add (n, i) for i in test()))

最后n是全局变量,它的值是10, 所以最右边的 test()产生的值是0, 1, 2, 3, 所以第一个in处和add函数作用后产生的值是10,11,12,13,最后再依次和10相加,所以这个答案是20, 21, 22, 23

 

posted @ 2019-07-15 22:05  yscl  阅读(98)  评论(0)    收藏  举报