四时宝库

程序员的知识宝库

Python 迭代器与可迭代对象,还在傻傻分不清楚吗

引言:迭代对象和迭代器到底是个啥

小白:专家,能给我讲讲 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'), ...]

小白:(献上膝盖)原来迭代器这么强大!
专家:(扶起小白)在实际编程中多使用它们,你会对它们有更深入的理解!

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接