一文读懂Python生成器和迭代器
  EC6Nu0EjeE86 2023年11月02日 110 0

在python中,我们经常会遇到需要对一系列的元素进行遍历或处理的情况,例如对列表中的每个元素进行求和或排序,或者对文件中的每一行进行读取或写入。为了实现这样的功能,我们通常会使用for循环或while循环来逐个获取元素,并进行相应的操作。例如:

对列表中的每个元素进行求和

lst = [1, 2, 3, 4, 5]
sum = 0
for x in lst:
    sum += x
print(sum) # 输出15

对文件中的每一行进行读取

f = open("test.txt", "r")
for line in f:
    print(line) # 输出文件内容
f.close()

在这些例子中,我们使用了一个非常重要且常见的概念:可迭代对象(iterable)。可迭代对象是指可以被for循环或其他迭代工具所遍历或处理的对象,它包含了一系列的元素,并且提供了一种方法来访问这些元素。在python中,很多内置的数据结构都是可迭代对象,如列表、元组、字典、集合、字符串等。我们也可以自定义类来实现可迭代对象,只要实现了__iter__()方法或者__getitem__()方法。

那么,当我们对一个可迭代对象进行迭代时,究竟发生了什么呢?实际上,当我们使用for循环或其他迭代工具对一个可迭代对象进行迭代时,python会自动调用该对象的__iter__()方法,该方法会返回一个迭代器(iterator)。迭代器是一个特殊的对象,它实现了__next__()方法,并且可以记住当前的迭代位置。每次调用迭代器的__next__()方法,它会返回可迭代对象中的下一个元素,直到没有更多的元素时,抛出一个StopIteration异常。例如:

创建一个可迭代对象

lst = [1, 2, 3, 4, 5]

调用可迭代对象的__iter__()方法,返回一个迭代器

it = iter(lst)

调用迭代器的__next__()方法,返回可迭代对象中的下一个元素

print(next(it)) # 输出1
print(next(it)) # 输出2
print(next(it)) # 输出3
print(next(it)) # 输出4
print(next(it)) # 输出5
print(next(it)) # 抛出StopIteration异常

从上面的代码可以看出,迭代器是一个非常有用的工具,它可以让我们方便地访问可迭代对象中的元素,而不需要知道可迭代对象的内部结构或实现细节。我们也可以自定义类来实现迭代器,只要实现了__iter__()方法和__next__()方法。例如:

定义一个斐波那契数列类,实现了可迭代对象和迭代器的接口

class Fibonacci:
    def __init__(self, n):
        self.n = n # 斐波那契数列的长度
        self.a = 0 # 第一个数
        self.b = 1 # 第二个数
        self.i = 0 # 当前的索引

    def __iter__(self):
        return self # 返回自身作为迭代器

    def __next__(self):
        if self.i < self.n: # 如果还有下一个元素
            x = self.a # 记录当前的数
            self.a, self.b = self.b, self.a + self.b # 更新下一个数和下下一个数
            self.i += 1 # 更新当前的索引
            return x # 返回当前的数
        else: # 如果没有下一个元素
            raise StopIteration # 抛出StopIteration异常

创建一个斐波那契数列对象,长度为10

fib = Fibonacci(10)

对斐波那契数列对象进行迭代,打印每个元素

for x in fib:
    print(x) # 输出0, 1, 1, 2, 3, 5, 8, 13, 21, 34

从上面的代码可以看出,我们可以通过自定义类来实现任意复杂的迭代逻辑,只要遵循了可迭代对象和迭代器的接口。但是,这样做也有一些缺点,如:

我们需要编写很多样板代码,如__iter__()方法和__next__()方法。 我们需要手动维护当前的迭代状态,如索引、变量等。 我们需要手动处理迭代结束的情况,如抛出异常等。 为了解决这些问题,python提供了一种更简洁而强大的工具:生成器(generator)。生成器是一种特殊的函数,它使用了yield关键字来返回一个值,并且暂停执行。当再次调用生成器时,它会从上次暂停的地方继续执行,直到遇到下一个yield关键字或者函数结束。生成器本质上也是一种迭代器,它可以被for循环或其他迭代工具所遍历或处理。使用生成器,我们可以用更简单而优雅的方式来实现复杂的迭代逻辑,而不需要编写很多样板代码或维护很多状态。例如:

定义一个斐波那契数列生成器函数,使用yield关键字返回每个数

def fibonacci(n):
    a = 0 #

接下来,我们将看看如何使用生成器函数,以及它们的优势和局限性。

要使用生成器函数,我们只需要像调用普通函数一样,传入相应的参数,并赋值给一个变量。这个变量就是一个生成器对象,它实现了迭代器的接口,可以被for循环或其他迭代工具所遍历或处理。例如:

# 创建一个斐波那契数列生成器对象,长度为10
fib = fibonacci(10)

# 对斐波那契数列生成器对象进行迭代,打印每个元素
for x in fib:
    print(x) # 输出0, 1, 1, 2, 3, 5, 8, 13, 21, 34

从上面的代码可以看出,使用生成器函数非常简单而方便,我们不需要编写很多样板代码或维护很多状态。生成器函数还有以下的优势:

生成器函数是惰性的,它只在需要时才计算下一个元素,而不是一次性生成所有的元素。这样可以节省内存空间和计算时间,特别是对于大规模或无限的数据集。 生成器函数是可组合的,我们可以将多个生成器函数连接起来,形成一个复杂的数据流。例如,我们可以使用itertools库中提供的各种生成器函数来实现各种排列、组合、过滤、映射等操作。 生成器函数是可重用的,我们可以多次调用同一个生成器函数,并得到相同的结果。例如,我们可以将同一个生成器对象传递给不同的函数或类,并进行不同的处理。 当然,生成器函数也有一些局限性,如:

生成器函数是单向的,我们只能从前往后获取元素,而不能从后往前或者跳跃获取元素。如果我们想要随机访问元素,我们需要将生成器对象转换成列表或其他数据结构。 生成器函数是一次性的,我们只能遍历一次元素,而不能重复遍历元素。如果我们想要多次遍历元素,我们需要重新创建生成器对象或者使用itertools.tee()函数来复制生成器对象。 生成器函数是不可预知的,我们无法提前知道元素的个数或者类型。如果我们想要获取这些信息,我们需要遍历所有的元素或者使用其他方法来估计。 这样,我们就介绍了什么是迭代器和生成器,它们有什么区别和联系。在下一个主题中,我们将介绍如何使用内置的迭代器和生成器函数,如range、enumerate、zip、map、filter等。请继续关注我的教程!

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
EC6Nu0EjeE86