目录
学习迭代器之前我们先要学习一下可迭代对象。
1.可迭代对象(Iterable):
在 Python 中,可迭代对象(Iterable)是指可以逐一遍历其元素的对象。大部分容器(Container)都可以看作是可迭代对象,例如列表(list)、元组(tuple)、字典(dict)、集合(set)、字符串(str)等等。这些类型的对象都可以使用 for...in... 语句对其进行遍历。
# 列表
for i in [1, 2, 3, 4, 5]:
print(i)
# 元组
for i in (1, 2, 3, 4, 5):
print(i)
# 字符串
for char in 'hello':
print(char)
# 字典
for key, value in {'a': 1, 'b': 2, 'c': 3}.items():
print(key, value)
# 集合
for i in {1, 2, 3, 4, 5}:
print(i)
对于列表(list)、元组(tuple)、字符串(str),这些对象包含多个元素,Python 提供了iter()方法来获取每个元素,所以它们是可迭代的。对于字典(dict),Python 提供了 iter() 方法来获取它的键,可以通过 keys() 方法获取所有的键,通过 values() 方法获取所有的值,或者通过 items() 方法获取所有的键值对,所以字典也是可迭代的。集合(set)数据类型也提供了 iter() 方法,用于获取集合中的每一个元素,所以集合也是可迭代的。
2.迭代器对象(Iterator):
这时候我们要介绍一下iter()
函数和next()
函数:
1.iter()
函数:iter()
是 Python 的内置函数,它会尝试调用对象的 __iter__
方法,并返回由该方法返回的迭代器,也就是说iter()
函数的主要功能就是接收一个可迭代对象,然后返回一个迭代器对象。(具体来说,iter()
函数会尝试调用传入对象的 __iter__()
方法,这个方法需要返回一个迭代器对象。这个迭代器对象需要实现了 __next__()
方法,用于不断获取或计算下一个值,直到抛出StopIteration
异常表示迭代结束。)
这里需要注意的是,并非所有对象都可以使用iter()
,它必须是一个可迭代对象。例如,我们熟知的列表、元组、字符串、字典、文件、生成器等,都是可迭代对象,因为它们的类都实现了 __iter__()
方法。
2.next()
函数:next()
是Python的一个内置函数,它用于获取迭代器的下一个项目。这个函数接受两个参数:第一个参数是迭代器对象,第二个参数是可选的,默认值为None
,如果迭代器耗尽,那么返回这个默认值。如果你没有提供第二个参数,并且迭代已经到达了末尾,那么next()
函数将会触发一个StopIteration
异常,表示迭代已经完成。
3.iter()
和next()
用法举例:
my_list = [1, 2, 3]
my_iter = iter(my_list) # 将列表迭代对象转化为迭代器对象
print(next(my_iter)) # 输出: 1
print(next(my_iter)) # 输出: 2
print(next(my_iter)) # 输出: 3
# 现在迭代已经到达末尾
# 如果没有提供第二个参数,next()会触发StopIteration异常
# print(next(my_iter))
# 如果提供了第二个参数,next()会返回这个值,并不会触发异常
print(next(my_iter, '没有更多元素了')) # 输出: "没有更多元素了"
3.可迭代对象和迭代器对象的关联与区别:
可迭代对象(Iterable)和迭代器对象(Iterator)是Python的两个重要概念,它们之间存在紧密的联系,但也有明显的区别。
可迭代对象(Iterable)是指可以使用 iter()
函数生成迭代器的对象,包含 __iter__()
方法。例如,列表、字符串、字典、集合等都是可迭代对象。
my_list = [1, 2, 3]
iter_obj = iter(my_list) # my_list 就是一个可迭代对象
迭代器对象(Iterator)则是指实现了 __next__()
方法和 __iter__()
方法(返回自身)的对象。通过调用 next()
函数或者 __next__()
方法可以获取下一项,如果没有更多的元素,则会抛出 StopIteration
异常。迭代器对象从某种意义上来说,也是一种可迭代对象,因为它有 __iter__()
方法。
class MyIterator:
# 使用关键字 class 来定义一个名为MyIterator的类
def __init__(self):
# 定义初始化函数 __init__,在创建类的实例(对象)时,会自动调用这个方法。
self.num_list = [1, 2, 3]
# 在对象被创建时,会在对象的命名空间中创建一个名为 num_list 的变量并赋值为 [1, 2, 3]
self.index = -1
# 在对象被创建时,会在对象的命名空间中创建一个名为 index 的变量并赋值为 -1
def __iter__(self):
# 定义 __iter__ 方法,该方法在对实例进行迭代时被调用,例如在 for loop 中。如果一个类定义了此方法,那么它是一个可迭代对象。
return self
# 这个方法返回迭代器对象自己,这是迭代器协议的规定。
def __next__(self):
# 定义了 __next__() 方法,该方法返回迭代器的下一个元素。如果所有元素都被迭代完毕,则抛出 StopIteration 异常。
self.index += 1
# 每调用一次 __next__ 方法,index 就增加 1
if self.index != len(self.num_list):
# 判断 index 是否大于 num_list 的长度,如果未超出,则返回 num_list 中相应位置上的元素。
return self.num_list[self.index]
# 如果 index 小于 num_list 的长度,返回对应位置的元素
raise StopIteration
# 如果 index 等于 num_list 的长度,那么就抛出 StopIteration 异常,表示迭代完毕,停止迭代。
my_iterator = MyIterator()
# 创建 MyIterator 类的实例,并赋值给变量 my_iterator
for i in my_iterator:
# 通过 for 循环来遍历 my_iterator 实例对象,这会自动调用 my_iterator 的 __iter__ 方法,并不断调用 __next__() 方法获取元素,
print(i)
区别:
-
可迭代对象主要用于表示一系列元素,具有迭代能力,但是不具备生成下一个元素的能力。
-
迭代器对象不仅具备迭代能力,还具备持续生成下一个元素的能力,直到耗尽所有元素。
-
迭代器可以进行惰性计算,它记录当前的状态,仅在需要的时候通过
next()
函数获取下一个值,这对于处理大规模或者无限的序列特别有用。可迭代对象则无法进行此类操作,要创建和存储整个序列。 -
迭代器只能被遍历一次,遍历完毕后,如果需要重新遍历,就需要再次使用
iter()
函数生成新的迭代器。可迭代对象则可以被多次遍历。
4.迭代器的优缺点:
在Python中使用迭代器有许多优点,但也存在一些缺点,具体如下:
优点:
-
内存效率:迭代器并不需要一次性加载所有数据到内存中,这意味着无论数据有多大,都可以迭代,因此对内存的使用非常有效率。
-
代码简洁:迭代器提供了一种干净简洁的编程语法,使得循环更简单,代码更易读。
-
通用性:迭代器可以用于任何可迭代的对象,比如列表、元组、字符串、字典、文件等,这提供了极大的便利性。
-
延迟计算(惰性评估):迭代器在需要时才计算下一个值,延迟了所有计算,可以提高性能和响应性。
缺点:
-
无法重用:迭代器不能重复使用。一旦元素被迭代读取,如果不重新创建迭代器,是不能够再次访问这些元素的。
-
保存状态:迭代器必须自己保存状态,因此可能会写出比普通代码更多、更复杂的代码。
-
只能单向前进:迭代器只能向前移动,不能向后移动,也不能跳跃读取,这有时对使用带来不便。
-
无法获取元素数量:通过迭代器我们无法事先知道数据的大小,除非把所有数据都迭代一遍。
总的来说,迭代器很好地平衡了内存使用、代码简洁性和处理大数据的能力。在考虑是否使用迭代器时,需要权衡这些优缺点。
5.生成器(generator):
1.生成器和迭代器的关系:生成器是一种特殊的迭代器。也就是说,任何生成器都是迭代器,它们都支持next()
操作。
2.生成器的特点:
-
生成器通过
yield
关键字来返回值。当生成器执行到yield
时,会暂停并保存当前所有的运行信息,以便下次调用next()
方法时,从上一次暂停的地方继续运行。 -
使用生成器可以节省内存,特别适合用于大数据的处理,只需用到的数据才会被存储和处理。
3.yield工作原理:
-
当你首次调用一个包含
yield
语句的函数时,函数会运行,直到遇到yield
语句为止。此时,函数值(即,yield
语句后面的表达式的值)会被返回,同时,函数的内部状态会被保存,包括当前执行到的位置以及所有的局部变量的值。 -
当你再次调用这个生成器函数(通常是通过
next()
函数或for...in
循环),函数会从保存的状态恢复,然后从上次yield
语句之后的下一条语句开始执行,直到遇到下一个yield
语句。
4. yield 和 return 区别
yield可以用于返回值,但不同于return。函数一旦遇到return就结束了,销毁上下文(弹出栈帧),将控制权返回给调用者。而yield可以保存函数的运行状态,挂起函数,用来返回多次值。因此,以 yield 进行执行流控制的函数称为生成器函数,以 return 进行执行流控制的函数就是普通函数。由于可以临时挂起函数的执行,yield 可以充当其调用者和被挂起函数间交互的桥梁。
5.生成器使用示例:
def my_generator(data):
for item in data:
yield item
gen = my_generator([1, 2, 3, 4, 5])
for item in gen:
print(item)
# 输出:
1
2
3
4
5
6.send()和close():
它们主要与生成器和yield
表达式一起使用
send()方法:send()
方法是生成器的一个非常重要的功能,它可以在生成器暂停的时候向其发送一个值,然后将此值作为暂停时yield
表达式的结果,这样就可以实现生成器中的代码与外部代码之间的数据交互。
具体来说,当你开始迭代一个生成器时,首次应当使用next()
函数,之后可以使用send()
方法(除非用send(None))。在调用send()
方法后,生成器会从上次暂停的地方(yield
表达式)继续执行,并使用send()
方法传入的值作为yield
表达式的值。
以下是代码示例:
def my_generator():
value_from_send = yield "hello"
yield value_from_send
gen = my_generator()
print(next(gen)) # 输出: 'hello'
print(gen.send('world')) # 输出: 'world'
close()
方法:
close()
方法用于停止生成器。调用generator.close()
后,生成器内部抛出一个GeneratorExit
异常。在生成器内部,可以捕获这个异常用来做清理工作。如果GeneratorExit
异常在生成器函数环境内(除了忽略以外)没有被处理,生成器会在下一次next()
或send()
调用时抛出StopIteration
异常。
def my_generator():
try:
while True:
yield 'Hello world!'
except GeneratorExit:
print('Generator has been closed!')
gen = my_generator()
next(gen) # 输出: 'Hello world!'
gen.close() # 输出: 'Generator has been closed!'
在某些情况下,你可能会想结构化生成工作,让生成器在完成后自动关闭。这可以通过使用with
语句结合contextlib.closing()
函数来实现。例如:
import urllib.request
from contextlib import closing
with closing(urllib.request.urlopen('http://www.python.org')) as page:
for line in page:
print(line)
7.生成器优点:
-
内存效率:生成器并不需要一次性加载所有数据到内存中,它会在每次迭代时生成下一个值。这意味着无论数据有多大,都可以迭代,因此对内存的使用非常有效率。
-
节省计算量:只有在需要时才会生成下一个值。如果你只需要前几个值,那么生成器会节省大量不必要的计算。
-
代码简洁:生成器提供了一种干净简洁的方式来编写迭代器。不需要再手动实现
__iter__
和__next__
等方法,简化了代码的编写。 -
无需了解迭代内部逻辑:使用生成器时,只需了解如何获取下一个值,而无需了解迭代的内部实现逻辑,使代码更易维护和理解。
-
延迟操作:生成器推迟了计算过程,只有在需要下一个元素的时候才计算。这在处理大数据量或无限序列的时候特别有用。
6.总结:
可迭代对象(Iterable):
-
可迭代对象是任何可以作为一个序列进行迭代的对象。在Python中,字符串、列表、元组、集合、字典都是可迭代对象。
-
这些对象实现了
__iter__()
方法,该方法将返回一个迭代器。 -
可以使用
for
循环直接遍历可迭代对象。
迭代器(Iterator):
-
迭代器是一个可以记住迭代的位置的对象。
-
它实现了
__next__()
方法,可以返回序列的下一项,如果没有更多的项,将抛出StopIteration
异常。 -
迭代器实现了
__iter__()
方法,使得迭代器也是可迭代的。
生成器(Generator):
-
生成器是一种特殊的迭代器,可以使用简单的方式创建。
-
生成器使用
yield
表达式,每次yield
时,函数会暂停并保存当前的状态,然后在下次迭代时从保存的状态恢复。 -
生成器是懒加载的,每次只生成一个值,只有在请求下一个值时才继续执行,这样可以节省内存。
总结来说,可迭代对象(Iterable)可以被迭代(即可以通过 for
循环获取每个元素),迭代器(Iterator)可以记住当前迭代到哪个元素,并可以获取下一个元素,生成器(Generator)是一种特殊的迭代器,它是懒加载的,即只有在需要获取下一个元素时才会计算。