迭代器与生成器
迭代器(iterator)
定义
迭代是可以通过遍历的方式依次把某个对象中的元素取出的方法,在python中,迭代是通过使用for…in…语句完成的
Python中的 for
循环本质上是一种遍历迭代器(iterator)中元素的方式。在每次循环迭代时,Python会自动调用迭代器的 __next__()
方法,将其所返回的下一个元素赋值给循环变量,并执行循环体中的语句。当迭代器中的元素全部被遍历完时,for
循环自动停止迭代。
可迭代对象
能够被for循环的对象就是可迭代对象
集合数据类型:str,list,tuple,dict,set…
生成器(generator),包括生成器和带yield的生成器函数
其实可迭代对象有更具体的定义方式 - 但凡内置方法中有__iter__和__next__方法的对象就是迭代器对象。
其中,__iter__
和 __next__
是迭代器类中必须实现的两个方法。__iter__
方法返回当前对象本身作为迭代器,并在迭代开始前进行初始化;__next__
方法返回下一个迭代器值,如果到达迭代器末尾,则触发 StopIteration
异常。
具体来说,在 Python 中,实现迭代器的过程如下:
定义一个迭代器类,并在
__init__
方法中初始化迭代器状态。实现
__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
具体来说,iter()
函数会调用对象的 __iter__()
方法,而 next()
函数则会调用迭代器对象的 __next__()
方法
在python中,for语句本质就是不断调度next函数实现的,for循环的对象一定是可迭代对象,因此for循环也被称为迭代器循环,for循环的原理就是基于迭代器的。
使用迭代器来实现一个斐波那契数列
斐波那契数列是指这样一个数列:1,1,2,3,5,8,13,21,34,55,89...... 这个数列从第三项开始,每一项都等于前两项之和
# -*- 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__,如果没有写,则会报错
需要注意的是,如果一个对象实现了 __iter__
方法,那么该对象就是可迭代的,也就是说,我们可以使用 iter()
函数获取其迭代器。如果对象没有实现 __iter__
方法,但是实现了 __getitem__
方法,那么该对象也是可迭代的,它的迭代器会按照索引依次获取元素。
class Fibonacci:
def __getitem__(self, index):
a, b = 1, 1
for _ in range(index):
a, b = b, a + b
return a
fib = Fibonacci()
for i in range(10):
print(fib[i])
# 输出
1
1
2
3
5
8
13
21
34
55
Process finished with exit code 0
__getitem__
是 Python 的一个魔法方法(Magic Method),它用于实现索引运算符 []
的行为。通过在一个类中定义 __getitem__
方法,可以使该类的对象支持索引操作,可以像列表或字典一样使用中括号访问其元素
__getitem__
方法的具体语法如下:
Codedef __getitem__(self, key):
# 索引操作的具体实现
其中,key
表示要获取元素的下标或键,我们需要根据该参数在对象中找到对应的值并将其返回。
在 Python 中,很多内置的数据类型(如列表、字典、元组等)都已经实现了 __getitem__
方法,因此我们可以直接使用中括号来访问这些对象的元素。此外,也可以在自定义的类中实现该方法,以使得该类的对象支持索引操作。
生成器 (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 方法可以把外部的值传入生成器内部,从而改变生成器的状态。
send(value)
方法:在生成器中向 yield
语句发送值,并恢复其执行。如果生成器在 yield
语句处等待,那么该值将成为 yield
表达式的结果。可以通过 next()
方法或者 __next__()
方法来启动生成器并将第一个值发送给它。示例如下:
# -*- coding: utf-8 -*-
def foo():
while True:
x = yield
print(x)
f = foo()
next(f)
f.send('Hello')
# 输出
Hello
在生成器函数中,第一次调用
next()
或者send(None)
是必须的,因为它们会启动生成器并运行到第一个yield
语句处等待进一步的指令。如果直接调用send()
方法,而没有先使用next()
启动生成器,则会得到一个TypeError
异常。
close()
方法
close()
方法:用于关闭生成器,释放相关资源并结束其执行。
一个示例代码如下所示,其中我们定义了一个生成器函数 foo
,它不断地产生斐波那契数列元素。在主程序中,我们使用 for
循环遍历该生成器对象,并通过 break
语句提前结束循环。接着,我们调用生成器对象的 close()
方法来关闭生成器。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2023/4/22 9:26
# @Author : shisuiyi
# @File : demo_推导式.py
# @Software: win10 Tensorflow1.13.1 python3.9
def foo():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
f = foo()
for x in f:
print(x)
if x > 100:
f.close()
break
# 输出
0
1
1
2
3
5
8
13
21
34
55
89
144
Process finished with exit code 0
throw
方法
throw(type[, value[, traceback]])
方法:用于在生成器中抛出一个指定类型、值、回溯信息的异常,并恢复其执行。如果在生成器中没有处理这种类型的异常,那么异常会向上冒泡,直到被捕获或传递给调用函数。示例如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2023/4/22 9:26
# @Author : shisuiyi
# @File : demo_推导式.py
# @Software: win10 Tensorflow1.13.1 python3.9
def foo():
while True:
try:
x = yield
y = int(x)
print(y ** 2)
except ValueError as e:
print("Invalid input:", e)
f = foo()
next(f)
f.send("5")
f.send("10")
f.send("A")
f.throw(ValueError, "Invalid input: A")
f.send("3")
# 输出
25
100
Invalid input: invalid literal for int() with base 10: 'A'
Invalid input: Invalid input: A
9
Process finished with exit code 0
评论