基本概念
在前面的章节中,我们已经学习了 Python 提供的for
语句和while
语句。这两种语句可以帮助开发人员以循环的方式逐个访问序列中的元素。
举个例子:
lst = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for elem in lst:
print(elem)
在这个例子中,我们通过for
语句,以循环的方式,实现了对lst
序列中所有元素的遍历。
通过“循环”这个概念,我们可以进一步引申出“迭代”的概念。
循环,众所周知,就是复执行一段代码一定次数或直到满足特定条件;相对应的,任何实现这种重复执行代码的机制,就叫做迭代(Iteration)。
换句话说,在编程中,迭代就是重复一组指令或操作的总体概念。
相对而言,迭代是一个比循环更广泛的概念,它涵盖了与编程中的重复相关的各种技术和过程。迭代不仅仅限于循环;相反,循环只是迭代的一种具体实现。也就是说,我们可以将循环视为迭代的一个子集或一种具体类型。
在 Python 中,如果我们希望重复执行一组指令--也就是实现迭代,我们可以采用循环的方式,也可以采用诸如递归、列表推导、生成器等其他方式。
在本节中,我们就将深入地探究一下迭代在 Python 中的应用。
Python 中的迭代和可迭代对象
我们想学习迭代在 Python 中的应用,就离不开一个概念:可迭代对象(Iterables),因为 Python 中的迭代通常是基于可迭代对象的。
那么,什么是可迭代对象呢?
可迭代对象,首先是 Python 中的对象;其次,外部代码可以遍历其包含的每个元素。像我们之前学习的列表、元组、字符串、字典、集合等,都是 Python 中的可迭代对象,因为我们可以逐一访问它们包含的元素。
简而言之,我们可以把可迭代对象看作是迭代过程的数据源。
在 Python 中,像迭代这种重复执行代码的过程,通常都是基于可迭代对象进行的;换言之,在 Python 中,如果没有可迭代对象,就无法进行迭代。
在 Python 中,我们常见的可迭代对象包括:
- 序列(如列表、元组、字符串)
- 集合(如集合、字典)
- 文件对象
这些可迭代对象与其他数据类型对象有什么区别呢?
- 迭代协议(Iteration Protocol):
可迭代对象遵循迭代协议,这意味着它们可以与迭代结构(如for
循环和while
循环)一起使用,从而允许外部代码可以逐个访问其元素;相比之下,其他数据类型对象则不需遵循迭代协议,外部代码也无法遍历其内部元素。 __iter__()
方法:
可迭代对象具有一个__iter__()
方法,该方法返回一个迭代器对象。迭代器负责跟踪迭代的当前状态,并在请求时使用__next__()
方法提供下一个项目。__next__()
方法:
可迭代对象返回的迭代器具有__next__()
方法,该方法产生序列中的下一个项目,或在没有更多项目可迭代时触发StopIteration
异常。这就允许外部代码可以逐个遍历可迭代对象的元素。- 序列或集合:
可迭代对象通常是数据的序列或集合,例如列表、元组、字符串、字典、集合等等。这些数据结构设计用于保存多个项目,并且可以通过迭代来访问其元素。 - 延迟求值:
一些可迭代对象,如生成器和生成器表达式,使用延迟求值。也就是说,当创建这些对象时,它们并不会马上生成所有值;相反,它们会在外部代码遍历它们时,才即时生成值。这对于处理大型数据集可以节省内存。
正是由于可迭代对象拥有这些特点,我们才可以在类似for
循环或者while
循环中进行元素遍历;相对应的,我们在普通的数据类型对象中,是无法做到这一点的。
那么,除了 Python 内置的诸多可迭代对象外,我们可以创建自定义的可迭代对象吗?
答案是:可以!
下面就让我们来学习一下如何在 Python 中创建自定义的可迭代对象。
自定义可迭代对象
在 Python 中,如果我们想要创建自定义的可迭代对象,我们需要通过遵循迭代协议来实现。一般而言,要使一个类可迭代,我们需要定义两个特殊方法:__iter__()
和__next__()
。
__iter__()
方法:
__iter__()
方法负责返回一个迭代器对象。迭代器是一个对象,它跟踪迭代的当前状态,并知道在请求时如何生成序列中的下一个项目。__next__()
方法:
__next__()
方法负责在每次迭代期间生成序列中的下一个项目。它确定下一个要返回的值,并推进迭代器的内部状态。
当我们在for
循环或其他迭代结构中使用可迭代对象时,Python 的调用逻辑是这样的:
- Python 调用可迭代对象上的
__iter__()
方法以获取一个迭代器。这是迭代的初始化步骤。 - Python 重复调用迭代器上的
__next__()
方法以获取序列中的下一个项目。每次循环时,都会调用__next__()
方法,并且迭代器会前进到下一个项目。 - 如果
__next__()
方法触发了StopIteration
异常,则表示迭代结束,循环终止。
下面让我们看看如何在具体开发时,定义这两个方法。
假如,我们已经创建了一个自定义类。
class MyIterable:
def __init__(self, data):
self.data = data
为了让这个类可迭代,我们需要在类中实现__iter__()
方法。
class MyIterable:
def __init__(self, data):
self.data = data
def __iter__(self):
self.index = 0
return self
在__iter__()
方法中,我们初始化一个index
属性,用于在迭代期间跟踪当前位置,并返回self
作为迭代器。
接下来,我们需要实现__next__()
方法。__next__()
方法负责在每次迭代期间返回序列中的下一个项目。如果没有更多的项目可迭代,它应该出发StopIteration
异常。
class MyIterable:
def __init__(self, data):
self.data = data
def __iter__(self):
self.index = 0
return self
def __next__(self):
if self.index < len(self.data):
result = self.data[self.index]
self.index += 1
return result
else:
raise StopIteration
这样,我们就创建了一个自定义可迭代类,我们可以在for
循环或任何其他迭代结构中使用它,就像使用任何 Python 内置的可迭代对象一样。
my_data = [1, 2, 3, 4, 5]
my_iterable = MyIterable(my_data)
for item in my_iterable:
print(item)
这段代码使用我们新创建的自定义可迭代对象,然后逐个遍历my_data
中的元素。
迭代器与next()
方法
我们在前面讲过,Python 中的列表、元组、集合和字符串等数据类型都是可迭代对象。这意味着我们可以使用for
循环或其他迭代结构来遍历它们的元素;但这并不意味着所有可迭代对象都支持next()
方法。
这里需要澄清的是,当我们在使用for
循环进行迭代时,我们无需使用next()
方法,因为 Python 在幕后为我们处理迭代过程。例如:
my_list = [1, 2, 3, 4]
for item in my_list:
print(item)
在这种情况下,Python 会自动遍历my_list
的元素。
当然,我们也可以显式地为这些数据类型创建一个迭代器,然后使用next()
函数逐个访问它们的元素。要实现这一点,我们需要使用iter()
函数获取可迭代对象的迭代器,然后在该迭代器上调用next()
。
例如:
my_list = [1, 2, 3, 4]
my_iterator = iter(my_list)
while True:
try:
item = next(my_iterator)
print(item)
except StopIteration:
break
在这个例子中,我们使用iter()
显式地为my_list
创建了一个迭代器,然后在while
循环中使用next()
函数逐个访问其元素。这种方法可以让开发人员更精细地控制迭代过程。
在 Python 中,几乎所有的序列类型对象都可以通过iter()
函数转换成迭代器。
例如,我们可以使用以下代码将列表 [1, 2, 3, 4, 5]
转换成迭代器:
lst = [1, 2, 3, 4, 5]
x = iter(lst)
print(x) # 输出:<list_iterator object at 0x10bf717b8>
我们也可以使用以下代码将元组 (1, 2, 3, 4, 5)
转换成迭代器:
t = (1, 2, 3, 4, 5)
x = iter(t)
print(x) # 输出:<tuple_iterator object at 0x10bf717b8>
我们还可以使用以下代码将字符串 'hello'
转换成迭代器:
s = 'hello'
x = iter(s)
print(x) # 输出:<str_iterator object at 0x10bf717b8>
可能有人会问,既然我们可以使用循环来遍历序列中的元素,为什么还要使用迭代器呢?
使用迭代器而不是传统循环来访问列表中的每个元素,其实会给我们带来很多好处,最主要的一个是:内存效率。
如前所述,迭代器提供了一种延迟求值的形式。也就是说,迭代器并不会在内存中预先计算和存储所有值,相反,它会在我们遍历序列时即时生成值。显而易见,这大幅降低了内存消耗,也避免了内存耗尽,因此迭代器在处理大型或无限数据集时,特别有帮助。
同时,由于迭代器不会在内存中预先存储所有值,所以,我们可以用迭代器来表示无限序列;而由于内存空间的有限性,我们是不可能使用传统的列表方式来存储无限序列的。
总结
迭代是 Python 中的一个基本概念,它允许开发人员高效地处理数据集合和执行重复任务。
在 Python 中,迭代是基于可迭代对象的。这些可迭代对象的目的是允许外部代码可以顺序访问其元素。
我们可以使用诸如for
循环的语句来遍历内置的可迭代对象,如列表和字符串;还可以通过定义带有 __iter__()
和 __next__()
方法的类来创建自定义可迭代对象。