迭代器和生成器
首先说说python容器:
容器是一种把Python对象存放在一起的数据结构,对于容器来说,我们可以逐个的迭代获取里面的元素,也可以使用in or not in 判断容器中是否有我们想要的对象.在Python中常见的容器对象有:
- list []
- dict {}
- set {}
- tuple ()
- 生成器
list1 = [1,2,3]
print(4 in list1)
>>False
print(1 in list1)
>>True
for obj in list1:
print(obj)
>>1
>>2
>>3
如何判断一个对象是否可迭代?
使用isinstance判断对象是不是Iterable类的实例
from collections import Iterable
print(isinstance([],Iterable))
>>True
print(isinstance(1,Iterable))
>>False
如何判断一个对象是否是迭代器?
from collections import Iterator
print(isinstance([], Iterator))
>>False
print(isinstance(iter([]), Iterator))
>>True
print(isinstance(iter("abc"), Iterator))
>>True
我们如何实现一个可迭代对象呢?
我们需要知道在Python中万物皆对象,想要一个对象是可迭代对象,只需要在对象的创建类里面添加__iter__魔法方法即可,__iter__魔法方法作用就是让一个对象可迭代
比如我们先新建一个不可迭代的类MyIter
class MyIter:
def __init__(self):
pass
myiter1 = MyIter() # 实例化出来的对象是不可迭代的,对不可迭代的对象遍历,会引发异常
for data in myiter1:
print(data)
>>TypeError: 'MyIter' object is not iterable
下面通过__iter__魔法方法让我们的不可迭代类MyIter变为可迭代的类
class MyIter:
def __init__(self):
pass
def __iter__(self):
return # 这里我们直接return,先什么都不做
myiter1 = MyIter()
for data in myiter1: # 一样会触发异常,这是因为我们并没有在__iter__里面返回一个迭代器或者生成器
print(data)
>>TypeError: iter() returned non-iterator of type 'NoneType'
# 不过没关系,他已经是一个可迭代对象了,请看
from collections import Iterable
print(isinstance(myiter1,Iterable))
>>True # 结果已经显示为可迭代了
接下来我们解决for循环TypeError的问题,在iter魔法方法里面返回一个迭代器或者一个生成器,生成器会在迭代器下面进行讲解,这里我们先返回一个迭代器
class MyIter:
def __init__(self):
pass
def __iter__(self):
return iter([1,2,3]) # 注意是迭代器,不是可迭代对象
myiter1 = MyIter()
from collections import Iterable
print(isinstance(myiter1,Iterable))
>>True
for data in myiter1:
print(data)
>>1
>>2
>>3
注意:迭代器和生成器都是一次性的,不能回滚,如果想要再次使用,需要重新实例化
下面再次使用myiter1,是可以继续得到1,2,3的
这是因为我们的写法原因,我们这里的写法每次都会重新return一个迭代器
for data in myiter1:
print(data)
>>1
>>2
>>3
下面修改我们的写法,来演示一次性效果
class MyIter:
def __init__(self):
self.can_iter = iter([1,2,3])
def __iter__(self):
return self.can_iter
myiter1 = MyIter()
for data in myiter1:
print(data)
print("--------------")
for data in myiter1:
print(data)
输出结果如下
>>1
>>2
>>3
>>--------------
可以看到第二个for循环什么都没有输出,如果我们想要再次使用就需要再次实例化
myiter2 = MyIter()
for data in myiter2:
print(data)
>>1
>>2
>>3
可以看到上面的代码并没有继续出现TypeError的问题,所以我们可以看出__iter__魔法方法的实际作用就是为了返回一个迭代器或者生成器的.
对于迭代器和生成器我们也可以使用next函数去获取迭代器or生成器里面的内容,不过当内容获取完之后会触发StopIteration异常
iter1 = iter([0,1,2])
print(next(iter1))
print(next(iter1))
print(next(iter1))
print(next(iter1))
>>0
>>1
>>2
Traceback (most recent call last):
StopIteration
但是需要注意的是如果使用for循环并不会看到StopIteration这个异常
for data in iter1:
print(data)
程序执行后什么都不会输出,且看不到StopIteration异常.
什么都不输出是因为迭代器是一次性的,不能回退,所以没有内容可输出
而为什么没有StopIteration异常,这是因为for循环会捕获这个异常,当for循环捕捉到StopIteration,就会停止迭代
由上面的代码我们可以总结出for...in 循环的本质
for 循环对可迭代对象进行迭代的过程:
1.首先调用__iter__魔法方法,获取到迭代器或生成器
2.使用next函数获取迭代器和生成器的下一个值
3.当没有值可获取之后,触发StopIteration异常反馈给for循环
4.for循环捕获StopIteration异常,并停止循环
其实问题说到这就会发现上面__iter__方法返回的迭代器,是用iter函数创建出来的,我们能不能自己定义一个迭代器供iter方法返回呢?答案是可以,请继续看这篇文章
如何自定义一个迭代器
自定义一个迭代器需要类的构造中要有__next__方法.这个方法的作用就是能够记录我们已经迭代到的位置,当没有元素可以迭代之后触发StopIteration异常.
下面我们的做法是给自己的可迭代类添加一个__next__方法,变成一个迭代器,同时通过__iter__方法返回我们实例本身
就以生成斐波那契数列为例:
import sys
class MyIter:
def __init__(self, max_size=None):
self.prev_num = 0
self.current_num = 1
self.index = 0
self.max_size = max_size
if max_size is None:
self.max_size = sys.maxsize
def __iter__(self):
return self
def __next__(self):
if self.index >= self.max_size:
raise StopIteration
self.index += 1
return_num =self.prev_num
self.prev_num = self.current_num
self.current_num += return_num
return return_num
myiter1 = MyIter() # 实例化一个不限制大小的迭代器
for i in range(10):
print(next(myiter1), end=" ")
print()
>>0 1 1 2 3 5 8 13 21 34
myiter2 = MyIter(10) # 限制大小
for j in myiter2:
print(j, end=" ")
print()
>>0 1 1 2 3 5 8 13 21 34
由上面的例子得出我们可以使用__next__
方法创造出各种各样算法的迭代器
使用迭代器的好处
如果我们需要一批数据,又知道数据产生的方法,那么我们就可以使用迭代器来生产数据,而不是事先生成放在内存中
注意
在Python中并不是只有for
循环可以迭代,list
和tuple
也可以迭代
比如你使用高级函数map
和filter
,返回的是一个迭代器,直接print
是看不到内容的,那么有两个选择,for
循环遍历,使用list
迭代转换为列表打印这个列表
生成器
生成器其实就是特殊的迭代器
创建生成器的方法一:改造列表生成式
list1 = [x for x in range(5)]
print(list1)
>>[0, 1, 2, 3, 4]
generate1 = (x for x in range(5))
print(generate1)
>><generator object <genexpr> at 0x0000000001EF5938>
print(list(generate1)) # list迭代转换
>>[0, 1, 2, 3, 4]
generate2 = (x for x in range(5))
print(next(generate2))
>>0
print(next(generate2))
>>1
print(next(generate2))
>>2
print(next(generate2))
>>3
print(next(generate2))
>>4
print(next(generate2))
Traceback (most recent call last):
StopIteration
方式二:改造函数(yield)
同样以斐波那契数列为例
import sys
def generate_feibo(max_size=None):
if max_size is None:
max_size = sys.maxsize
prev_num = 0
current_num = 1
index = 0
while index < max_size:
return_num =prev_num
yield return_num
index += 1 # 这一步放yield前面和后面执行效果都一样
prev_num = current_num
current_num += return_num
return
myiter1 = generate_feibo() # 实例化一个不限制大小的生成器
for i in range(3):
print(next(myiter1), end=" ")
>>0 1 1
print()
myiter2 = generate_feibo(3)
for data in myiter2:
print(data, end=" ")
>>0 1 1
注
使用yield
之后,就不需要手动抛出StopIteration
异常了,当生成器没有使用yield
返回数据,而是使用return
结束函数的时候会自动抛出StopIteration
异常
使用生成器的实例二:读取一个超大文件
def read_file():
with open("./oid.txt","r",encoding="utf-8",newline="\n") as f:
while True:
data = f.readline().strip()
if data is None or not data:
break
yield data
return