引言:迭代对象和迭代器到底是个啥
小白:专家,能给我讲讲 Python 里的迭代器和可迭代对象吗?感觉这两个概念有点模糊。
专家:当然可以。简单来说,可迭代对象就是能在 for 循环中使用的对象,像列表、元组、字典、字符串这些常见的数据类型都是可迭代对象。比如我们有一个列表:
my_list = [1, 2, 3, 4, 5]
for item in my_list:
print(item)
在这段代码里,my_list就是一个可迭代对象,我们可以用for循环遍历它里面的每一个元素。
小白:那迭代器又是什么呢?
专家:迭代器是一种特殊的对象,它不仅是可迭代对象(实现了__iter__方法),还实现了__next__方法。__next__方法用于逐个返回元素,当没有更多元素时,会抛出StopIteration异常。迭代器有点像一个指针,每次调用__next__方法,它就会指向下一个元素。
比如,我们可以把刚才的列表转换成迭代器:
my_list = [1, 2, 3, 4, 5]
my_iter = iter(my_list)
print(next(my_iter)) # 输出1
print(next(my_iter)) # 输出2
print(next(my_iter)) # 输出3
print(next(my_iter)) # 输出4
print(next(my_iter)) # 输出5
# print(next(my_iter)) # 这行会抛出StopIteration异常
这里通过iter()函数把列表my_list转换成了迭代器my_iter,然后用next()函数不断获取迭代器中的下一个元素。但如果不是迭代器,使用next函数就会报错,比如:
next(my_list)
#会报错如下
Traceback (most recent call last):
File "test.py", line 9, in
next(my_list)
TypeError: 'list' object is not an iterator
可迭代对象和迭代器有什么关系
小白:那可迭代对象和迭代器有什么关系呢?
专家:所有的迭代器都是可迭代对象,但可迭代对象不一定是迭代器。就像我们上面看到的列表,它是可迭代对象,但不是迭代器。要判断一个对象是否是可迭代对象,可以使用isinstance函数结合Iterable类型来判断,判断是否是迭代器则用Iterator类型。
from typing import Iterable, Iterator
my_list = [1, 2, 3, 4, 5]
print(isinstance(my_list, Iterable)) # 输出True
print(isinstance(my_list, Iterator)) # 输出False
my_iter = iter(my_list)
print(isinstance(my_iter, Iterable)) # 输出True
print(isinstance(my_iter, Iterator)) # 输出True
闭坑指南:这里要注意,不要对非可迭代对象使用iter()函数,否则会报错。比如对一个整数使用iter():
iter(10) # 这行会报错,TypeError: 'int' object is not iterable
常见的可迭代对象
小白:明白了,那还有哪些常见的可迭代对象呢?
专家:除了列表,元组、字典、集合、字符串都是可迭代对象。例如:
# 元组
my_tuple = (1, 2, 3)
for item in my_tuple:
print(item)
# 字典
my_dict = {'a': 1, 'b': 2, 'c': 3}
for key, value in my_dict.items():
print(key, value)
# 集合
my_set = {1, 2, 3}
for item in my_set:
print(item)
# 字符串
my_str = "hello"
for char in my_str:
print(char)
闭坑指南:在遍历字典时,如果在循环中修改字典的大小(比如添加或删除键值对),可能会导致不可预测的结果。所以尽量不要在遍历字典时修改它。
自定义迭代对象和迭代器
小白:那我们可以自己定义可迭代对象和迭代器吗?
专家:当然可以。要定义一个可迭代对象,我们需要在类中实现__iter__方法。如果要定义一个迭代器,除了__iter__方法,还需要实现__next__方法。
举例:传入一个数字,返回被2整除的大于0的数字。
class MyIterator():
def __init__(self,digitnum):
self.digitnum=digitnum
self.current_num = 0
def __iter__(self):
print('调用了__iter__方法')
return self
def __next__(self):
print('调用了__next__方法')
while True:
if self.current_num >= self.digitnum:
raise StopIteration
if self.current_num % 2 == 0 and self.current_num != 0:
temp_num = self.current_num
self.current_num += 1
return temp_num
self.current_num += 1
实例化后执行for循环
myiterator = MyIterator(11)
#for循环自定义的迭代器
for i in myiterator:
print(i)
#结果如下:
调用了__iter__方法
调用了__next__方法
2
调用了__next__方法
4
调用了__next__方法
6
调用了__next__方法
8
调用了__next__方法
10
调用了__next__方法
这里MyIterator类实现了__iter__和__next__方法,所以它是一个迭代器。在for循环中使用它时,会自动调用__iter__方法获取迭代器对象,然后不断调用__next__方法获取下一个偶数,直到达到指定的限制。
闭坑指南:在实现__next__方法时,一定要注意在合适的时候抛出StopIteration异常,否则循环不会停止,可能导致程序陷入死循环。
生成器也是一种迭代器
小白:听说生成器和迭代器也有关系,能讲讲吗?
专家:没错,生成器其实是一种特殊的迭代器。有两种方式创建生成器,一种是使用生成器表达式,另一种是在函数中使用yield关键字。
1)我们用生成器表达式创建一个生成器,它能生成 1 到 10 之间的偶数:
my_generator = (i for i in range(1, 11) if i % 2 == 0)
print(isinstance(my_generator, Iterable)) # 输出True
print(isinstance(my_generator, Iterator)) # 输出True
for num in my_generator:
print(num)
2)用函数结合yield关键字实现同样功能的生成器:
在函数中使用yield关键字,而不是return关键字,代表该函数是生成器。生成器也是一种迭代器,可以使用for循环,每当函数执行到yield时会暂停,等待下次在该位置继续执行。
上面讲到的传入一个数字,返回被2整除的大于0的数用生成器实现如下。
def MyIterator(num):
current_num = 0
while current_num <= num:
print (f'调用生成器 current_num : {current_num}')
if current_num % 2==0 and current_num!=0:
temp_num = current_num
yield temp_num
current_num += 1
#生成器也是一种迭代器
print(f'生成器 是否是可迭代对象 : {isinstance(MyIterator(10), Iterable)}')
print(f'生成器 是否是迭代器 : {isinstance(MyIterator(10), Iterator)}')
#结果:
生成器 是否是可迭代对象 : True
生成器 是否是迭代器 : True
执行for循环
for i in MyIterator(10):
print(i)
#结果:
调用生成器 current_num : 0
调用生成器 current_num : 1
调用生成器 current_num : 2
2
调用生成器 current_num : 3
调用生成器 current_num : 4
4
调用生成器 current_num : 5
调用生成器 current_num : 6
6
调用生成器 current_num : 7
调用生成器 current_num : 8
8
调用生成器 current_num : 9
调用生成器 current_num : 10
10
闭坑指南:生成器是一次性的,一旦遍历完,就不能再次使用。如果需要再次使用,需要重新创建生成器对象。
使用itertools模块创建迭代器
import itertools
# 无限迭代器
counter = itertools.count(start=10, step=2)
print(next(counter)) # → 10
print(next(counter)) # → 12
# 排列组合
perms = itertools.permutations('ABC', 2)
print(list(perms)) # → [('A','B'), ('A','C'), ...]
小白:(献上膝盖)原来迭代器这么强大!
专家:(扶起小白)在实际编程中多使用它们,你会对它们有更深入的理解!