Python基础 迭代器与生成器

Python基础 迭代器与生成器

迭代器

可迭代对象(iterable)

但凡是可以返回一个迭代器的对象都可称之为可迭代对象,看个例子

  1. >>> x = [1, 2, 3] 
  2. >>> y = iter(x) 
  3. >>> z = iter(x) 
  4. >>> next(y) 

  5. >>> next(y) 

  6. >>> next(z) 

  7. >>> type(x) 
  8. <class 'list'> 
  9. >>> type(y) 
  10. <class 'list_iterator'> 

这里x是一个可迭代对象,这只是一种通俗的叫法,并不是一种数据类型.
y和z是两个独立的迭代器,迭代器内部持有一个状态,该状态用于记录当前迭代所在的位置,以方便下次迭代的时候获取正确的元素。

迭代器(iterator)

那什么是迭代器呢?它是一个带状态的的对象,他能在你调用next()方法的时候返回容器中的下一个值,任何实现了__iter__() 和 __ next__() 方法的对象都是迭代器,__iter__返回迭代器自身,__next__返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常.

例子

  1. # 生成无限序列 
  2. >>> from itertools import count 
  3. >>> counter = count(start=13) 
  4. >>> next(counter) 
  5. 13 
  6. >>> next(counter) 
  7. 14 
  1. # 从有限序列中生成无限序列 
  2. >>> from itertools import count 
  3. >>> counter = count(start=13) 
  4. >>> next(counter) 
  5. 13 
  6. >>> next(counter) 
  7. 14 
  1. # 为了更直观地感受迭代器内部的执行过程,我们自定义一个迭代器,以斐波那契数列为例 
  2. class Fib: 
  3. def __init__(self): 
  4. self.prev = 0 
  5. self.curr = 1 
  6.  
  7. def __iter__(self): 
  8. return self 
  9.  
  10. def __next__(self): 
  11. value = self.curr 
  12. self.curr += self.prev 
  13. self.prev = value 
  14. return value 
  15.  
  16. >>> f = Fib() 
  17. >>> list(islice(f, 0, 10)) 
  18. [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] 

Fib既是一个可迭代对象(因为它实现了__iter__方法),又是一个迭代器(因为实现了__next__方法)。实例变量prev和curr用户维护迭代器内部的状态。每次调用next()方法的时候做两件事:

1.为下一次调用next()方法修改状态
2 为当前这次调用生成返回结果

生成器

什么是生成器?

  通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的,而且创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

  所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间,在Python中,这种一边循环一边计算的机制,称为生成器:generator

  生成器是一个特殊的程序,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。

  生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器(实际就是迭代器)

python中的生成器

  要创建一个generator,有很多种方法,第一种方法很简单,只有把一个列表生成式的[]中括号改为()小括号,就创建一个generator

  1. #列表生成式 
  2. lis = [x*x for x in range(10)] 
  3. print(lis) 
  4. #生成器 
  5. generator_ex = (x*x for x in range(10)) 
  6. print(generator_ex) 
  7.  
  8. 结果: 
  9. [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 
  10. <generator object <genexpr> at 0x000002A4CBF9EBA0> 

那么创建lis和generator_ex,的区别是什么呢?从表面看就是[ ]和(),但是结果却不一样,一个打印出来是列表(因为是列表生成式),而第二个打印出来却是<generator object at 0x000002A4CBF9EBA0>,那么如何打印出来generator_ex的每一个元素呢?

  如果要一个个打印出来,可以通过next()函数获得generator的下一个返回值:

  1.  
  2. #生成器 
  3. generator_ex = (x*x for x in range(10)) 
  4. print(next(generator_ex)) 
  5. print(next(generator_ex)) 
  6. print(next(generator_ex)) 
  7. print(next(generator_ex)) 
  8. print(next(generator_ex)) 
  9. print(next(generator_ex)) 
  10. print(next(generator_ex)) 
  11. print(next(generator_ex)) 
  12. print(next(generator_ex)) 
  13. print(next(generator_ex)) 
  14. print(next(generator_ex)) 
  15. 结果: 




  16. 16 
  17. 25 
  18. 36 
  19. 49 
  20. 64 
  21. 81 
  22. Traceback (most recent call last): 
  23.  
  24. File "列表生成式.py", line 42, in <module> 
  25.  
  26. print(next(generator_ex)) 
  27.  
  28. StopIteration 

  大家可以看到,generator保存的是算法,每次调用next(generaotr_ex)就计算出他的下一个元素的值,直到计算出最后一个元素,没有更多的元素时,抛出StopIteration的错误,而且上面这样不断调用是一个不好的习惯,正确的方法是使用for循环,因为generator也是可迭代对象:

  1. #生成器 
  2. generator_ex = (x*x for x in range(10)) 
  3. for i in generator_ex: 
  4. print(i) 
  5.  
  6. 结果: 




  7. 16 
  8. 25 
  9. 36 
  10. 49 
  11. 64 
  12. 81 

  所以我们创建一个generator后,基本上永远不会调用next(),而是通过for循环来迭代,并且不需要关心StopIteration的错误,generator非常强大,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

  这里说一下generator和函数的执行流程,函数是顺序执行的,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次被next()调用时候从上次的返回yield语句处急需执行,也就是用多少,取多少,不占内存。

原文地址:https://www.cnblogs.com/FelixTeng/p/9898039.html