python迭代器与生成器详解
__iter__
和__next__
先看一个开发中常见的for循环的使用:
a=[1,2,3]
for i in a:
print(i)
1
2
3
a是一个列表,本身也是一个迭代器(iterator);for循环能够遍历的一定是iterator,在遍历时会默认调用迭代器的iter()方法,将其显示出来,即等价于:
for i in iter(a):
print(i)
1
2
3
在理解迭代器之前,必须要理解魔法方法__iter__
和__next__
的调用。上面例子iter(a)的背后就是调用对象a的__iter__
方法,即a.__iter__()
;
iter(a)返回的是什么?返回的是一个内部实现了__next__()
方法的对象,每次调用__next__()
来弹出下一个值,以此来完成遍历。
举例:
class A(object):
def __init__(self,a):
self.a=a
def __next__(self):
print('this is next')
if self.a<6:
self.a+=1
return self.a
else:
raise StopIteration
class B(object):
def __init__(self):
pass
def __iter__(self):
print('this is iter')
return A(a=0)
>>>b=B()
>>>for i in b:#本质上是调用iter(b),或者b.__iter__();返回了一个实现了__next__方法的实例对象。
>>> print(i)
this is iter#首先调用iter(b)
this is next#调用A的next,返回值为当前的i。
1
this is next
2
this is next
3
this is next
4
this is next
5
this is next
6
this is next
#for 循环会自动捕获StopIteration异常。
值得注意的是两个概念:
- 可迭代对象(Iterable):类中实现了
__iter__
的就可以叫iterable了,一般__iter__
返回的是一个实现了__next__
方法的对象实例。这一点不强制。如果自己本身就实现了__next__
方法,自然就可以返回self。 - 迭代器(Iterator):类中实现了
__iter__
和__next__
方法的对象。缺少任何一个都不能叫iterator;迭代器自然也是一个iterable。
如上面A,B两个类的实例对象,A(0)既不是Iterable也不是Iterator;B()是Iterable但不是Iterator
from collections.abc import Iterable,Iterator
>>>isinstance(a, Iterable)
False
>>>isinstance(a, Iterator)
False
>>>isinstance(b, Iterable)
True
>>>isinstance(b, Iterator)
False
python中内置的可迭代对象:列表、元组、字典、集合字符串和open()打开的文件等都是;注意这些都不是迭代器Iterator;因为对于迭代器,我们不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。而明显列表、元组、字典等我们是可以掌握全局信息的。
迭代器Iterator
根据__iter__
和__next__
来定义自己的迭代器:
class Myrange(object):
def __init__(self,stop,start=0):
self.start=start
self.stop=stop
def __iter__(self):
return self
def __next__(self):
if self.start<self.stop:
res=self.start
self.start+=1
else:
raise StopIteration
return res
for i in Myrange(3):
print(i)
0
1
2
>>>isinstance(Myrange(3),Iterable)
True
>>>isinstance(Myrange(3),Iterator)
True
>>>type(Myrange(3))
<class '__main__.Myrange'>
生成器
python提供了一种特殊的yield方法,来使函数对象直接转换为一个迭代器。生成器就是一类特殊的迭代器。每当程序执行到yield的时候,程序就暂停在这里,处于挂起状态,然后把返回的东西返回以后,再继续执行原来的程序。
def func(end):
start=0
while start<end:
yield start
start+=1
from collections.abc import Iterable,Iterator
>>>print(isinstance(func(3),Iterator))
>>>print(isinstance(func(3),Iterable))
>>>for i in func(3):
>>> print(i)
True#是一个可迭代对象
True#是一个迭代器
0
1
2
>>>a=func(4)
>>>print(next(a))
>>>print(a.__next__())
0
1
>>>print(type(a))
<class 'generator'>
>>>print(isinstance(a,Generator))#也是一个生成器类型。
<class 'generator'>
从迭代器或者生成器的原理可以看到,其最大的特点就是可以节省内存。同样遍历一个有100000万条整数的列表和生成器,消耗的内存是完全不一样的。列表需要全部把物理空间开辟出来来先保存这些数据,而生成器以此只需要保存一个数据即可。
import sys
>>>a=range(1000000)
>>>sys.getsizeof(a)
48
>>>b=list(range(1000000))
>>>sys.getsizeof(b)
8000056
而生成器使用的场景有节省内存的好处,有个前提就是我们能够按照某种算法推算出来,在循环的过程中不断推算出后续想要的元素。
总结
- 实现了
__iter__
方法的对象为可迭代对象(Iterable),常见的Iterable有list,dict,tuple,range等;同时实现了__iter__
和__next__
方法的为迭代器(Iterator);函数中应用yield关键字的对象为生成器(Generator),生成器也是一种迭代器和可迭代对象。 - 迭代器Iterator、生成器Generator都需要通过for循环,或者手动调用next()来逐个获取其内部值
- 迭代器、生成器的使用可以节省内存,但前提是我们能够按照某种算法推算出来,在循环的过程中不断推算出后续想要的元素。
- 生成器的其它使用场景可以参考https://www.zhihu.com/questio...
- 最后,容器、迭代器、生成器、可迭代对象的关系可以用下图总结:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。