迭代器(iterator)
定义
迭代是可以通过遍历的方式依次把某个对象中的元素取出的方法,在python中,迭代是通过使用for…in…语句完成的
可迭代对象
能够被for循环的对象就是可迭代对象
- 集合数据类型:str,list,tuple,dict,set…
- 生成器(generator),包括生成器和带yield的生成器函数
其实可迭代对象有更具体的定义方式 – 但凡内置方法中有__iter__和__next__方法的对象就是迭代器对象。
执行迭代器对象的__iter__方法得到的仍然是迭代器对象,执行迭代器对象的__next__方法就会得到迭代器对象中的下一个值。
注意: 是可迭代对象的,不一定是迭代器,是迭代器的,一定是可迭代对象,可以通过isinstance函数对其进行判断
from collections import Iterator
print(isinstance("123", Iterator)) # str类型,输出:False
如果我们想把他们转换为迭代器,可以使用iter函数
from collections import Iterator
print(isinstance(iter("123"), Iterator)) # str类型,输出:True
迭代对象的遍历
- 使用常规for语句进行遍历:
list = [1, 2, 3, 4]
it = iter(list) # 创建迭代器对象
for x in it:
print(x, end=" ")
# 输出
1 2 3 4
- 使用 next() 函数:
import sys # 引入 sys 模块
list = [1, 2, 3, 4]
it = iter(list) # 创建迭代器对象
while True:
try:
print(next(it), end=" ")
except StopIteration:
sys.exit()
# 输出
1 2 3 4
迭代器中有两个基本方法:iter(),next()。使用iter函数创建一个迭代器后,就可以通过next函数获取迭代器的下一个值,如果通过next()不断调用并返回下一个值,那么等到最后没有下一个值了,就会抛出异常:StopIteration
在python中,for语句本质就是不断调度next函数实现的,for循环的对象一定是可迭代对象,因此for循环也被称为迭代器循环,for循环的原理就是基于迭代器的。
使用迭代器来实现一个斐波那契数列
# -*- coding: utf-8 -*-
class Fibonacci:
def __init__(self, n):
self.n = n
self.a = 0
self.b = 1
def __iter__(self):
return self
def __next__(self):
if self.n > 0:
self.n -= 1
self.a, self.b = self.b, self.a + self.b
return self.a
else:
raise StopIteration
b = Fibonacci(11)
for i in b:
print(i)
# 输出:
1 1 2 3 5 8 13 21 34 55 89
注:我们需要在类中写两个魔法方法,iter__和__next,如果没有写,则会报错
生成器 (generator)
定义
在 Python 中,使用了 yield 的函数被称为生成器(generator)。
函数用return关键字来返回值,将函数体代码中的return关键字替换成yield关键字,再调用函数,不会执行函数体代码,得到的返回值就是一个生成器对象。
得到的生成器(generator)对象有内置方法__iter__和__next__,因此生成器本身就是自定义的迭代器。
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
生成器表达式
生成器表达式和列表推导式差不多,我们只需要包列表推导式的[]改为(),这样就是一个生成器表达式了。需要注意的是,列表推导式返回的是一个列表对象,而生成器表达式返回的是一个生成器对象,因此我们可以通过生成器表达式来创建一个生成器。优点自然是节省内存,一次只产生一个值存在内存中。
a = [i for i in range(1, 4)]
print(type(a)) # <class 'list'>
print(a) # [1, 2, 3]
print(next(a)) # TypeError: 'list' object is not an iterator
b = (i for i in range(1, 4))
print(type(b)) # <class 'generator'>
print(b) # <generator object <genexpr> at 0x0000021781CCEE40>
print(next(b)) # 1
生成器函数
将函数体代码中的return关键字替换成yield关键字,就有了自定义迭代器的实现方式,yield不同于return,函数一旦遇到return就结束了,但是yield可以保存函数的运行状态,用来返回多次值。
# -*- coding: utf-8 -*-
# 自定义一个可以产生很多数字的生成器,就像range一样
def my_range(start, stop, step=1):
while start < stop:
yield start
start += step
res = my_range(1, 10, 2)
print(res) # <generator object my_range at 0x0000028FF2F5A6C8>
print(res.__iter__()) # <generator object my_range at 0x0000028FF2F5A6C8>
print(res.__next__()) # 1
print(res.__next__()) # 3
print(res.__next__()) # 5
使用生成器来实现一个斐波那契数列
# -*- coding: utf-8 -*-
def Fibonacci(n):
b = 0
c = 1
while True:
if n > 0:
n -= 1
b, c = c, b + c
yield b
else:
break
#
res = Fibonacci(11)
print(res.__next__())
print(res.__next__())
print(res.__next__())
print('--------------------')
for i in res:
print(i)
# 输出
1
1
2
--------------------
3
5
8
13
21
34
55
89
使用yield读取文件
如果直接对文件对象调用read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取:
# -*- coding: utf-8 -*-
def read_file(path, encoding="utf-8"):
BLOCK_SIZE = 1024
with open(path, "r", encoding=encoding) as f:
block = f.read(BLOCK_SIZE)
if block:
yield block
else:
return None
res = read_file('card.txt')
for i in res:
print(i)
# 输出
0011527546,201,2001 #
0002470753,302,0000 #
3375355472,4,0000 #
send 方法
send 方法可以把外部的值传入生成器内部,从而改变生成器的状态。
# -*- coding: utf-8 -*-
import sys
import time
def gen():
i = 1
while True:
j = yield i
i *= 2
if j == -1:
break
g = gen() # 创建一个生成器
print(g.__next__()) # 1
print(g.__next__()) # 2
for i in g:
print(i)
if i == 32:
try:
# send 把 -1 传入生成器内部 走到了 j = -1 这个分支
print('我提前结束了')
print(g.send(-1)) # StopIteration 迭代停止
except StopIteration:
sys.exit()
time.sleep(1)
# 输出
1
2
4
8
16
32
我提前结束了
当我们执行 g.send(-1) 时,相当于把 -1 传入到了生成器内部,然后赋值给了 yield 前面的 j,此时 j = -1,然后这个方法就会 break 出来,不会继续迭代下去。否则输出结果会是 1 2 4 8 16 32 64 … 一直循环下去, 直到我们杀死这个进程才能停止。