迭代器和生成器的学习笔记
迭代器
Python 迭代器是一种对象,它实现了迭代协议,包括 __iter__()
和 __next__()
方法。迭代器可以让你在数据集中逐个访问元素,而无需关心数据结构的底层实现。与列表或其他集合相比,迭代器可以节省内存,因为它们一次只生成一个元素。
迭代器的基本特点
- 懒加载:迭代器不会一次性将所有数据加载到内存中,而是根据需要生成元素。
- 状态保持:迭代器在迭代过程中会保持其状态,可以从上次停止的地方继续迭代。
- 可以遍历:迭代器是可遍历的,可以使用
for
循环等结构来进行遍历。
下面的代码可以看出迭代器在节省内存方面的作用。
import sys
import random
# 生成 1-100的随机数的集合,共1000个元素
numbers = [random.randint(1, 100) for _ in range(1000)]
iterator = iter(numbers)
# 打印对象的内存大小
print(sys.getsizeof(numbers)) # 9016
print(sys.getsizeof(iterator)) # 48
迭代器的经典demo:
# 创建一个简单的迭代器
class MyIterator:
def __init__(self, limit):
self.limit = limit
self.current = 0
def __iter__(self):
return self
def __next__(self):
if self.current < self.limit:
self.current += 1
return self.current
else:
raise StopIteration
# 使用迭代器
for num in MyIterator(5):
print(num)
迭代器在读取大文件的经典应用:
with open('users.csv', 'r') as file:
for line in file:
print(line.strip())
逐行读取文件内容,而不需要将整个文件加载到内存中。这种方法节省了内存资源,特别是在处理非常大的文件时。
生成器
生成器是一种特殊类型的迭代器,它允许你在函数中暂停执行并在以后继续。
使用 yield
的基本概念
-
生成器函数:当一个函数包含
yield
语句时,它不再是一个普通的函数,而是一个生成器函数。当调用这个函数时,它不会立即执行,而是返回一个生成器对象。 -
状态保留:每次调用生成器的
__next__()
方法(或者使用next()
函数)时,生成器函数会从上次执行的位置继续执行,直到遇到下一个yield
语句。在此时,函数的执行状态(包括局部变量)会被保留。 -
迭代:生成器可以被用于迭代,像普通的列表或其他可迭代对象一样。使用
for
循环可以逐个获取生成器产生的值。
# 定义一个生成器函数
def read_users():
with open('users.csv', 'r') as file:
for line in file:
yield line.strip()
r1 = read_users() # 定义一个生成器对象
r2 = read_users() # 定义另一个生成器对象
print(next(r1))
print(next(r1))
print(next(r1))
print(next(r1))
print(next(r2))
print(next(r2))
print(next(r2))
print(next(r2))
局部变量保留的demo
list1 = ['a', 'b', 'c', 'd', 'e', 'f']
def iterator():
i = 0
for x in list1:
yield do_something(i, x)
i += 1
def do_something(i, x):
print(i, x)
r1 = iterator() # 定义一个生成器对象
r2 = iterator() # 定义另一个生成器对象
next(r1)
next(r1)
next(r1)
next(r1)
next(r2)
next(r2)
next(r2)
next(r2)
next(r2)
运行结果:
0 a
1 b
2 c
3 d
0 a
1 b
2 c
3 d
4 e
更深入理解yield的“暂停”
函数每次遇到yield就暂停,它并不在意yield时来自于循环、迭代或者是在函数体内重复定义的,这意味着一个函数中可以有不止一个yield,并且每次运行到yield时,函数就暂停运行,并保存中间结果和变量,直到下一次next()后继续运行。
list1 = ['a', 'b', 'c', 'd', 'e', 'f']
def iterator():
for x in list1:
yield print(x)
yield print(x)
yield print(x)
yield print(x)
r1 = iterator() # 定义一个生成器对象
next(r1)
next(r1)
next(r1)
next(r1)
next(r1)
next(r1)
# 运行结果:
# a
# a
# a
# a
# b
# b
可以将yield理解为一个中断标志
可以将yield理解为一个中断标志,当生成器遇到 yield
语句时,它会暂停执行,并返回 yield
后面跟随的值或者函数。如果yield后面没有跟随内容,那么它就仅仅是一次暂停标志而已。
list1 = ['a', 'b', 'c', 'd', 'e', 'f']
def iterator():
i = 0
for x in list1:
print('loop', i)
yield
i += 1
print(x)
r1 = iterator() # 定义一个生成器对象
next(r1)
next(r1)
next(r1)
# 执行结果:
# loop 0
# a
# loop 1
# b
# loop 2
yield的灵活运用
既然函数每次遇到yield就暂停,它并不在意yield时来自于循环、迭代或者是在函数体内重复定义的,而且可以将yield理解为一个中断标志,那么我们也就可以生成一个不循环的函数,通过yield达到步进执行的效果。
list1 = ['a', 'b', 'c', 'd', 'e', 'f']
def iterator():
i = 0
print('breakpoint', i)
yield
i += 1
print('breakpoint', i)
yield
i += 1
print('breakpoint', i)
yield
i += 1
print('breakpoint', i)
yield
i += 1
print('breakpoint', i)
r1 = iterator() # 定义一个生成器对象
next(r1)
next(r1)
next(r1)
# 执行结果:
# breakpoint 0
# breakpoint 1
# breakpoint 2
注意可暂停和可迭代次数
要保证调用的次数不要大于可迭代次数或者可暂停次数,否则就会报错。
def iterator():
i = 0
print('breakpoint', i)
i += 1
yield
print('breakpoint', i)
i += 1
yield
r1 = iterator() # 定义一个生成器对象
next(r1)
next(r1)
next(r1)
上面的例子,定义了两个yield,但是next()调用了三次,所以出错。
list1 = [1, 2, 3]
def iterator():
for i in list1: # 遍历列表
yield print(i)
r1 = iterator() # 定义一个生成器对象
next(r1)
next(r1)
next(r1)
next(r1)
这个,由于调用次数大于了列表的元素数量,也会出错。
采取措施,避免程序崩溃
def iterator():
i = 0
print('breakpoint', i)
i += 1
yield
print('breakpoint', i)
i += 1
yield
r1 = iterator() # 定义一个生成器对象
def next_do():
try:
next(r1)
except StopIteration:
print("No more items to yield")
next_do()
next_do()
next_do()
next_do()
list1 = [1, 2, 3]
def iterator():
for i in list1: # 遍历列表
yield print(i)
r1 = iterator() # 定义一个生成器对象
def next_do():
try:
next(r1)
except StopIteration:
print("No more items to yield")
next_do()
next_do()
next_do()
next_do()
next_do()
# 运行结果:
# 1
# 2
# 3
# No more items to yield
# No more items to yield