您现在的位置是:网站首页> 编程资料编程资料

详解Python中迭代器和生成器的原理与使用_python_

2023-05-26 339人已围观

简介 详解Python中迭代器和生成器的原理与使用_python_

关于python中迭代器,生成器介绍的文章不算少数,有些写的也很透彻,但是更多的是碎片化的内容。本来可迭代对象、迭代器、生成器概念就很绕,又加上过于碎片的内容,更让人摸不着头脑。本篇尝试用系统的介绍三者的概念和关系,希望能够帮助需要的人。

1.可迭代对象、迭代器

1.1概念简介

迭代:

首先看迭代的字面意思:

迭代的意思就是:迭代是一种行为,反复执行的动作。在python中可以理解为反复取值的动作。

可迭代对象:顾名思义就是可以从里面迭代取值的对象,在python中容器类的数据结构都是可迭代对象,如列表,字典,集合,元组等。

迭代器:类似于从可迭代对象中取值的一种工具,严谨的说可以将可迭代对象中的值取出的对象。

1.2可迭代对象

在python中,容器类型的数据结构都是可迭代对象,列举如下:

  • 列表
  • 字典
  • 元组
  • 集合
  • 字符串
>>> arr = ['圣僧','大圣','天蓬','卷帘'] >>> for i in arr: ... print(i) ... 圣僧 大圣 天蓬 卷帘 >>>

除了python自带的数据结构是可迭代对象之外,模块里的方法、自定义的类也可能是可迭代对象。那么如何确认一个对象是否为可迭代对象呢?有一个标准,那就是可迭代对象都有方法__iter__,凡是具有该方法的对象都是可迭代对象。

>>> dir(arr) ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] 

1.3迭代器

常见迭代器是从可迭代对象创建而来。调用可迭代对象的__iter__方法就可以为该可迭代对象创建其专属迭代器。使用iter()方法也可以创建迭代器,iter()本质上就是调用可迭代对象的__iter__方法。

>>> arr = ['圣僧','大圣','天蓬','卷帘'] >>> arr_iter = iter(arr) >>> >>> for i in arr_iter: ... print(i) ... 圣僧 大圣 天蓬 卷帘 >>> >>> >>> arr_iter = iter(arr) >>> next(arr_iter) '圣僧' >>> next(arr_iter) '大圣' >>> next(arr_iter) '天蓬' >>> next(arr_iter) '卷帘' >>> next(arr_iter) Traceback (most recent call last): File "", line 1, in  StopIteration 

可迭代对象只能通过for循环来遍历,而迭代器除了可以通过for循环来遍历,重要的是还可以通过next()方法来迭代出元素。调用一次迭代出一个元素,直到所有元素都迭代完,抛出StopIteration错误。这个过程就像象棋中没有过河的小卒子——只能前进不能后退,并且迭代完所有元素也无法再次遍历。

简单总结迭代器的特征:

  • 可以使用next()方法迭代取值
  • 迭代的过程只能向前不能后退
  • 迭代器是一次性的,迭代完所有元素就无法再次遍历,需要再次遍历只有新建迭代器

迭代器对象在python中很常见,比如打开的文件就是一个迭代器、map,filter,reduce等高阶函数的返回也是迭代器。迭代器对象拥有两个方法:__iter__ 和__next__next()方法能迭代出元素就是调用__next__来实现的。

>>> dir(arr_iter) ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__length_hint__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__'] 

1.4区分可迭代对象和迭代器

如何区分可迭代对象和迭代器呢?在python的数据类型增强模块collections中有可迭代对象和迭代器数据类型,通过isinstance类型比较即可区分出两者。

>>> from collections import Iterable, Iterator >>> arr = [1,2,3,4] >>> isinstance(arr, Iterable) True >>> isinstance(arr, Iterator) False >>> >>> >>> arr_iter = iter(arr) >>> isinstance(arr_iter, Iterable) True >>> isinstance(arr_iter, Iterator) True >>>

arr:可迭代对象。是可迭代对象类型,不是迭代器类型

arr_iter:迭代器。既是可迭代对象类型,又是迭代器类型

1.5可迭代对象和迭代器的关系

从迭代器的创建就能大致看出。可迭代对象就是一个集合,而迭代器就是为这个集合创建的迭代方法。迭代器迭代时是直接从可迭代对象集合里取值。可以用如下模型来理解两者之间的关系:

>>> arr = [1,2,3,4] >>> iter_arr = iter(arr) >>> >>> arr.append(100) >>> arr.append(200) >>> arr.append(300) >>> >>> for i in iter_arr: ... print(i) ... 1 2 3 4 100 200 300 >>>

可以看到这里的流程是:

  • 先创建可迭代对象arr
  • 然后从arr创建的arr_iter迭代器
  • 再向arr列表追加元素
  • 最后迭代出来的元素包括后追加的元素。

可以说明迭代器并不是copy了可迭代对象的元素,而是引用了可迭代对象的元素。在迭代取值时直接使用了可迭代对象的元素。

1.6可迭代对象和迭代器的工作机制

首先整理一下两者的方法

可迭代对象: 对象中有__iter__ 方法

迭代器:对象中有__iter__ 和 __next__方法

在迭代器的创建时提到过__iter__方法是返回一个迭代器,__next__是从元素中取值。所以,关于两者方法的功能:

可迭代对象

__iter__方法的作用是返回一个迭代器

迭代器

__iter__方法的作用是返回一个迭代器,就是自己。

__next__方法的作用是返回集合中下一个元素

可迭代对象是一个元素集合,本身没有自带取值的方法,可迭代对象就像老话说的茶壶里的饺子,有货倒不出。

>>> arr = [1,2,3,4] >>> >>> next(arr) Traceback (most recent call last): File "", line 1, in  TypeError: 'list' object is not an iterator 

既然饺子倒不出来,又想吃怎么办?那就得找筷子一样的工具来夹出来对吧。而迭代器就是给用来给可迭代对象取值的工具。

给可迭代对象arr创建的迭代器arr_iter,可以通过next取值,将arr中值全部迭代出来,直到没有元素抛出异常StopIteration

>>> arr_iter = iter(arr) >>> >>> next(arr_iter) 1 >>> next(arr_iter) 2 >>> next(arr_iter) 3 >>> next(arr_iter) 4 >>> next(arr_iter) Traceback (most recent call last): File "", line 1, in  StopIteration >>>

for 循环本质

>>> arr = [1,2,3] >>> for i in arr: ... print(i) ... 1 2 3 

以上通过for循环遍历出arr中所有值。我们知道列表arr是可迭代对象,本身无法取值,for循环如何迭代出所有元素呢?

for循环的本质就是给arr创建一个迭代器,然后不断调用next()方法取出元素,复制给变量i,直到没有元素抛出捕获StopIteration的异常,退出循环。可以通过模拟for循环更直观的说明:

arr = [1,2,3] # 给arr生成一个迭代器 arr_iter = iter(arr) while True: try: # 不断调用迭代器next方法,并捕获异常,然后退出 print(next(arr_iter)) except StopIteration: break >> 1 2 3 

到这里大概就讲完了可迭代对象和迭代器的工作机制,简单总结:

可迭代对象: 保存元素,但自身无法取值。可以调用自己的__iter__方法创建一个专属迭代器来取值。

迭代器:拥有__next__方法,可以从指向的可迭代对象中取值。只能遍历一遍,并且只能前进不能后退。

1.7自己动手创建可迭代对象和迭代器

榴莲好不好吃,只有尝一尝才知道。迭代器好不好理解,动手实现一次就清楚。下面自定义可迭代对象和迭代器。

如果自定义一个可迭代对象,那么需要实现__iter__方法;

如果要自定义一个迭代器,那么就需要实现__iter____next__方法。

可迭代对象:实现__iter__方法,功能是调用该方法返回迭代器

迭代器:实现__iter__,功能是返回迭代器,也就是自身;实现__next__,功能是迭代取值直到抛出异常。

from collections import Iterable, Iterator # 可迭代对象 class MyArr(): def __init__(self): self.elements = [1,2,3] # 返回一个迭代器,并将自己元素的引用传递给迭代器 def __iter__(self): return MyArrIterator(self.elements) # 迭代器 class MyArrIterator(): def __init__(self, elements): self.index = 0 self.elements = elements # 返回self,self就是实例化的对象,也就是调用者自己。 def __iter__(self): return self # 实现取值 def __next__(self): # 迭代完所有元素抛出异常 if self.index >= len(self.elements): raise StopIteration value = self.elements[self.index] self.index += 1 return value arr = MyArr() print(f'arr 是可迭代对象:{isinstance(arr, Iterable)}') print(f'arr 是迭代器:{isinstance(arr, Iterator)}') # 返回了迭代器 arr_iter = arr.__iter__() print(f'arr_iter 是可迭代对象:{isinstance(arr_iter, Iterable)}') print(f'arr_iter 是迭代器:{isinstance(arr_iter, Iterator)}') print(next(arr_iter)) print(next(arr_iter)) print(next(arr_iter)) print(next(arr_iter)) 

结果:

arr 是可迭代对象:True
arr 是迭代器:False

arr_iter 是可迭代对象:True
arr_iter 是迭代器:True

1
2
3
Traceback (most recent call last):
  File "myarr.py", line 40, in
    print(next(arr_iter))
  File "myarr.py", line 23, in __next__
    raise StopIteration
StopIteration

从这个列子就能清晰的认识可迭代对象的迭代器的实现。可迭代对象的__iter__方法返回值就是一个实例化的迭代器的对象。这个迭代器的对象保存了可迭代对象的元素的引用,也实现了取值的方法,所以可以通过next()方法取值。这是一个值得细品的代码,比如说有几个问题可以留给读者思考:

  • 为什么next()只能前进不能后退
  • 为什么迭代器只能遍历一次就失效
  • <

-六神源码网