迭代器是可以被迭代的对象。它们是 Python 编程语言中的常见特性,常用于循环与列表推导式。任何能够派生出迭代器的对象称为可迭代对象(iterable)。
构造一个迭代器需要做不少工作。例如,每个迭代器对象的实现必须包含 __iter__() 和 __next__() 方法。除了上述前提条件,实现还必须能够跟踪对象的内部状态,并在没有更多值可返回时抛出 StopIteration 异常。这些规则被称为迭代器协议 。
自行实现迭代器是一个冗长的过程,而且并非总是必要。一个更简单的替代方案是使用生成器对象。生成器是一种特殊的函数类型,使用 yield 关键字返回一个可以被迭代的迭代器,每次产生一个值。
能分辨在何种场景下实现迭代器、在何种场景下使用生成器,将提升您作为 Python 程序员的能力。在本教程的其余部分,我们将强调这两类对象的区别,帮助您在不同情境中做出最佳选择。
术语表
术语
定义
Iterable(可迭代对象)
一种可以在循环中被遍历的 Python 对象。常见的可迭代对象包括列表、集合、元组、字典、字符串等。
Iterator(迭代器)
一种可以被迭代的对象。因此,迭代器包含可数数量的值。
Generator(生成器)
一种特殊的函数类型,不返回单个值;它返回一个包含一系列值的迭代器对象。
Lazy Evaluation(惰性求值)
一种求值策略,只有在需要时才生成某些对象。因此,在某些开发者圈子中,惰性求值也被称为“按需调用(call-by-need)”。
Iterator Protocol(迭代器协议)
在 Python 中定义迭代器时必须遵循的一组规则。
next()
一个内置函数,用于返回迭代器中的下一个项目。
iter()
一个内置函数,用于将可迭代对象转换为迭代器。
yield()
一个与 return 类似的 Python 关键字,但 yield 返回的是生成器对象而非一个值。
Python 迭代器与可迭代对象
可迭代对象能够一次返回其成员——也就是说,它们可以被迭代。常见的内置Python 数据结构 (如列表、元组和集合)都是可迭代对象。其他数据结构如字符串和字典也被视为可迭代对象:字符串可以按字符迭代,字典的键也可以被迭代。经验法则:凡是在 for 循环中可以被迭代的对象,都可视为可迭代对象。
通过示例探索 Python 可迭代对象
根据定义,我们可以得出结论:所有迭代器也是可迭代对象。然而,并非每个可迭代对象都是迭代器。可迭代对象只有在被迭代时才会产生一个迭代器。
为演示这一点,我们将实例化一个列表(它是可迭代对象),并通过对该列表调用内置函数 iter() 来生成一个迭代器。
list_instance = [ 1 , 2 , 3 , 4 ]
print ( iter ( list_instance) )
"""
<list_iterator object at 0x7fd946309e90>
""" 技术支持
尽管列表本身不是迭代器 ,但调用 iter() 函数会将其转换为迭代器并返回该迭代器对象。
为了说明并非所有可迭代对象都是迭代器,我们将实例化相同的列表对象,并尝试调用用于返回迭代器中下一个项目的 next() 函数。
list_instance = [ 1 , 2 , 3 , 4 ]
print ( next ( list_instance) )
"""
--------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-2-0cb076ed2d65> in <module>()
3 print(iter(list_instance))
4
----> 5 print(next(list_instance))
TypeError: 'list' object is not an iterator
""" 技术支持
在上面的代码中,您可以看到对列表调用 next() 函数会引发 TypeError——了解更多请参阅Python 中的异常与错误处理 。出现这种行为,是因为列表对象是可迭代对象而不是迭代器。
通过示例探索 Python 迭代器
因此,如果目标是遍历列表,那么必须先生成一个迭代器对象。只有这样,我们才能通过迭代器来管理对列表值的迭代。
list_instance = [ 1 , 2 , 3 , 4 ]
iterator = iter ( list_instance)
print ( next ( iterator) )
print ( next ( iterator) )
print ( next ( iterator) )
print ( next ( iterator) )
"""
1
2
3
4
""" 技术支持
每当您尝试遍历可迭代对象时,Python 都会自动生成一个迭代器对象。
list_instance = [ 1 , 2 , 3 , 4 ]
for item in list_instance:
print ( item)
"""
1
2
3
4
""" 技术支持
当捕获到 StopIteration 异常时,循环结束。
从迭代器获取的值只能从左到右依次检索。Python 没有 previous() 函数来让开发者在迭代器中向后移动。
迭代器的惰性特性
可以基于同一个可迭代对象定义多个迭代器。每个迭代器都会维护自己的进度状态。因此,通过为一个可迭代对象定义多个迭代器实例,可以在一个实例迭代到末尾的同时,另一个实例仍然停留在开头。
list_instance = [ 1 , 2 , 3 , 4 ]
iterator_a = iter ( list_instance)
iterator_b = iter ( list_instance)
print ( f"A: { next ( iterator_a) } " )
print ( f"A: { next ( iterator_a) } " )
print ( f"A: { next ( iterator_a) } " )
print ( f"A: { next ( iterator_a) } " )
print ( f"B: { next ( iterator_b) } " )
"""
A: 1
A: 2
A: 3
A: 4
B: 1
""" 技术支持
请注意,iterator_b 打印的是序列的第一个元素。
因此,我们可以说迭代器具有惰性特性:创建迭代器时,元素并不会立刻产出,只有在被请求时才会返回。换言之,只有当我们显式调用 next(iter(list_instance)) 时,列表实例的元素才会被返回。
不过,也可以通过在迭代器对象上调用内置的可迭代数据结构容器(如 list()、set()、tuple())来一次性提取迭代器中的所有值,从而强制迭代器一次性生成其所有元素。
list_instance = [ 1 , 2 , 3 , 4 ]
iterator = iter ( list_instance)
print ( list ( iterator) )
"""
[1, 2, 3, 4]
""" 技术支持
对于大型迭代器不建议这样做,因为它会强制生成并同时在内存中保存每个元素,从而违背惰性求值的目的。
当数据集过大而难以在内存中舒适地容纳,或当您希望以惰性方式迭代而不想编写完整的迭代器类时,通常生成器更合适。
Python 生成器
实现迭代器最省事的替代方案是使用生成器。虽然生成器看起来像普通的Python 函数 ,但它们有所不同。首先,生成器对象并不直接返回项目。相反,它使用 yield 关键字按需生成项目。因此,我们可以说生成器是一种利用惰性求值 的特殊函数。
生成器不会像典型的可迭代对象那样将内容存储在内存中。举例来说,如果目标是找到一个正整数的所有因子,通常我们会实现一个传统函数(关于Python 函数 的更多内容请参阅本教程)如下所示:
def factors ( n) :
factor_list = [ ]
for val in range ( 1 , n+ 1 ) :
if n % val == 0 :
factor_list. append( val)
return factor_list
print ( factors( 20 ) )
"""
[1, 2, 4, 5, 10, 20]
""" 技术支持
上述代码返回了完整的因子列表。然而,请注意当使用生成器而不是传统 Python 函数时的差异:
def factors ( n) :
for val in range ( 1 , n+ 1 ) :
if n % val == 0 :
yield val
print ( factors( 20 ) )
"""
<generator object factors at 0x7fd938271350>
""" 技术支持
由于我们使用了 yield 而非 return ,函数在运行后并不会退出。实质上,我们告诉 Python 去创建一个生成器对象而不是传统函数,从而可以跟踪该生成器对象的状态。
因此,可以对这个惰性迭代器调用 next() 函数,以一次显示序列中的一个元素。
def factors ( n) :
for val in range ( 1 , n+ 1 ) :
if n % val == 0 :
yield val
factors_of_20 = factors( 20 )
print ( next ( factors_of_20) )
"""
1
""" 技术支持
创建生成器的另一种方式是使用生成器推导式。生成器表达式的语法与列表推导式相似,但使用圆括号而不是方括号。
factor_gen = ( val for val in range ( 1 , 21 ) if 20 % val == 0 )
print ( list ( factor_gen) )
"""
[1, 2, 4, 5, 10, 20]
""" 技术支持
探索 Python 的 yield 关键字
yield 关键字控制生成器函数的执行流。与使用 return 时会退出函数不同,yield 会返回函数但记住其局部变量的状态。
由 yield 调用返回的生成器可以被赋值给变量,并通过 next() 函数进行迭代——这会执行函数,直到遇到第一个 yield 关键字。一旦触达 yield,函数的执行被挂起。此时,函数的状态会被保存。因此,我们可以在需要时恢复函数执行。
函数会从上一次 yield 的位置继续执行。例如:
def yield_multiple_statements ( ) :
yield "This is the first statement"
yield "This is the second statement"
yield "This is the third statement"
yield "This is the last statement. Don't call next again!"
example = yield_multiple_statements( )
print ( next ( example) )
print ( next ( example) )
print ( next ( example) )
print ( next ( example) )
print ( next ( example) )
"""
This is the first statement
This is the second statement
This is the third statement
This is the last statement. Don't call next again or else!
--------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-25-4aaf9c871f91> in <module>()
11 print(next(example))
12 print(next(example))
---> 13 print(next(example))
StopIteration:
""" 技术支持
在上述代码中,我们的生成器有四次 yield 调用,但我们尝试对其调用了五次 next,结果引发了 StopIteration 异常。出现这种行为,是因为我们的生成器并非无限序列,超出预期次数的调用会耗尽生成器。
总结
回顾一下:迭代器是可以被迭代的对象,生成器是利用惰性求值的特殊函数。实现自定义迭代器意味着您必须创建 __iter__() 和 __next__() 方法,而生成器则可以通过在 Python 函数或推导式中使用 yield 关键字来实现。
当您需要一个具备复杂状态维护行为的对象,或者希望暴露除 __next__()、__iter__() 与 __init__() 之外的其他方法时,您可能更倾向于使用自定义迭代器。另一方面,在处理大型数据集(因为它们不将内容存储在内存中)或不需要实现完整迭代器时,生成器可能更为合适。
迭代器是可以被迭代的对象。它们是 Python 编程语言中的常见特性,常用于循环与列表推导式。任何能够派生出迭代器的对象称为可迭代对象(iterable)。
构造一个迭代器需要做不少工作。例如,每个迭代器对象的实现必须包含
__iter__()和__next__()方法。除了上述前提条件,实现还必须能够跟踪对象的内部状态,并在没有更多值可返回时抛出StopIteration异常。这些规则被称为迭代器协议。自行实现迭代器是一个冗长的过程,而且并非总是必要。一个更简单的替代方案是使用生成器对象。生成器是一种特殊的函数类型,使用
yield关键字返回一个可以被迭代的迭代器,每次产生一个值。能分辨在何种场景下实现迭代器、在何种场景下使用生成器,将提升您作为 Python 程序员的能力。在本教程的其余部分,我们将强调这两类对象的区别,帮助您在不同情境中做出最佳选择。
术语表
术语
定义
Iterable(可迭代对象)
一种可以在循环中被遍历的 Python 对象。常见的可迭代对象包括列表、集合、元组、字典、字符串等。
Iterator(迭代器)
一种可以被迭代的对象。因此,迭代器包含可数数量的值。
Generator(生成器)
一种特殊的函数类型,不返回单个值;它返回一个包含一系列值的迭代器对象。
Lazy Evaluation(惰性求值)
一种求值策略,只有在需要时才生成某些对象。因此,在某些开发者圈子中,惰性求值也被称为“按需调用(call-by-need)”。
Iterator Protocol(迭代器协议)
在 Python 中定义迭代器时必须遵循的一组规则。
next()
一个内置函数,用于返回迭代器中的下一个项目。
iter()
一个内置函数,用于将可迭代对象转换为迭代器。
yield()
一个与 return 类似的 Python 关键字,但
yield返回的是生成器对象而非一个值。Python 迭代器与可迭代对象
可迭代对象能够一次返回其成员——也就是说,它们可以被迭代。常见的内置Python 数据结构(如列表、元组和集合)都是可迭代对象。其他数据结构如字符串和字典也被视为可迭代对象:字符串可以按字符迭代,字典的键也可以被迭代。经验法则:凡是在 for 循环中可以被迭代的对象,都可视为可迭代对象。
通过示例探索 Python 可迭代对象
根据定义,我们可以得出结论:所有迭代器也是可迭代对象。然而,并非每个可迭代对象都是迭代器。可迭代对象只有在被迭代时才会产生一个迭代器。
为演示这一点,我们将实例化一个列表(它是可迭代对象),并通过对该列表调用内置函数
iter()来生成一个迭代器。尽管列表本身不是迭代器,但调用
iter()函数会将其转换为迭代器并返回该迭代器对象。为了说明并非所有可迭代对象都是迭代器,我们将实例化相同的列表对象,并尝试调用用于返回迭代器中下一个项目的
next()函数。在上面的代码中,您可以看到对列表调用
next()函数会引发TypeError——了解更多请参阅Python 中的异常与错误处理。出现这种行为,是因为列表对象是可迭代对象而不是迭代器。通过示例探索 Python 迭代器
因此,如果目标是遍历列表,那么必须先生成一个迭代器对象。只有这样,我们才能通过迭代器来管理对列表值的迭代。
每当您尝试遍历可迭代对象时,Python 都会自动生成一个迭代器对象。
当捕获到
StopIteration异常时,循环结束。从迭代器获取的值只能从左到右依次检索。Python 没有
previous()函数来让开发者在迭代器中向后移动。迭代器的惰性特性
可以基于同一个可迭代对象定义多个迭代器。每个迭代器都会维护自己的进度状态。因此,通过为一个可迭代对象定义多个迭代器实例,可以在一个实例迭代到末尾的同时,另一个实例仍然停留在开头。
请注意,
iterator_b打印的是序列的第一个元素。因此,我们可以说迭代器具有惰性特性:创建迭代器时,元素并不会立刻产出,只有在被请求时才会返回。换言之,只有当我们显式调用
next(iter(list_instance))时,列表实例的元素才会被返回。不过,也可以通过在迭代器对象上调用内置的可迭代数据结构容器(如
list()、set()、tuple())来一次性提取迭代器中的所有值,从而强制迭代器一次性生成其所有元素。对于大型迭代器不建议这样做,因为它会强制生成并同时在内存中保存每个元素,从而违背惰性求值的目的。
当数据集过大而难以在内存中舒适地容纳,或当您希望以惰性方式迭代而不想编写完整的迭代器类时,通常生成器更合适。
Python 生成器
实现迭代器最省事的替代方案是使用生成器。虽然生成器看起来像普通的Python 函数,但它们有所不同。首先,生成器对象并不直接返回项目。相反,它使用
yield关键字按需生成项目。因此,我们可以说生成器是一种利用惰性求值的特殊函数。生成器不会像典型的可迭代对象那样将内容存储在内存中。举例来说,如果目标是找到一个正整数的所有因子,通常我们会实现一个传统函数(关于Python 函数的更多内容请参阅本教程)如下所示:
上述代码返回了完整的因子列表。然而,请注意当使用生成器而不是传统 Python 函数时的差异:
由于我们使用了
yield而非return,函数在运行后并不会退出。实质上,我们告诉 Python 去创建一个生成器对象而不是传统函数,从而可以跟踪该生成器对象的状态。因此,可以对这个惰性迭代器调用
next()函数,以一次显示序列中的一个元素。创建生成器的另一种方式是使用生成器推导式。生成器表达式的语法与列表推导式相似,但使用圆括号而不是方括号。
探索 Python 的 yield 关键字
yield关键字控制生成器函数的执行流。与使用return时会退出函数不同,yield会返回函数但记住其局部变量的状态。由
yield调用返回的生成器可以被赋值给变量,并通过next()函数进行迭代——这会执行函数,直到遇到第一个yield关键字。一旦触达yield,函数的执行被挂起。此时,函数的状态会被保存。因此,我们可以在需要时恢复函数执行。函数会从上一次
yield的位置继续执行。例如:在上述代码中,我们的生成器有四次
yield调用,但我们尝试对其调用了五次 next,结果引发了StopIteration异常。出现这种行为,是因为我们的生成器并非无限序列,超出预期次数的调用会耗尽生成器。总结
回顾一下:迭代器是可以被迭代的对象,生成器是利用惰性求值的特殊函数。实现自定义迭代器意味着您必须创建
__iter__()和__next__()方法,而生成器则可以通过在 Python 函数或推导式中使用 yield 关键字来实现。当您需要一个具备复杂状态维护行为的对象,或者希望暴露除
__next__()、__iter__()与__init__()之外的其他方法时,您可能更倾向于使用自定义迭代器。另一方面,在处理大型数据集(因为它们不将内容存储在内存中)或不需要实现完整迭代器时,生成器可能更为合适。