geekdoc-python-zh/docs/pythonlibrary/python-201-an-intro-to-iter...

9.5 KiB
Raw Permalink Blame History

Python 201:迭代器和生成器介绍

原文:https://www.blog.pythonlibrary.org/2016/05/03/python-201-an-intro-to-iterators-and-generators/

自从开始用 Python 编程以来,你可能一直在使用迭代器和生成器,但你可能没有意识到这一点。在这篇文章中,我们将学习什么是迭代器和生成器。我们也将学习它们是如何被创建的,这样我们就可以在需要的时候创建我们自己的。

迭代程序

迭代器是一个允许你遍历容器的对象。Python 中的迭代器通过两种不同的方法实现: iternext 。您的容器需要 iter 方法来提供迭代支持。它将返回迭代器对象本身。但是如果您想要创建一个迭代器对象,那么您还需要定义 next ,这将返回容器中的下一个项目。

注意:在 Python 2 中,命名约定略有不同。你仍然需要 iter ,但是 next 被称为 next 为了让事情更加清楚,让我们回顾一下几个定义:

  • iterable -定义了 iter 方法的对象
  • iterator -同时定义了 iternext 的对象,其中 iter 将返回迭代器对象,而 next 将返回迭代中的下一个元素。

与大多数神奇的方法(带有双下划线的方法)一样,您不应该直接调用 iternext。相反,你可以使用一个进行循环或列表理解Python 会自动为你调用这些方法。在某些情况下,您可能需要调用它们,但是您可以使用 Python 的内置函数来这样做: iternext

在我们继续之前我想提一下序列。Python 3 有列表、元组、范围等几种序列类型。该列表是可迭代的,但不是迭代器,因为它不实现 next。这在下面的例子中很容易看出:


>>> my_list = [1, 2, 3]
>>> next(my_list)
Traceback (most recent call last):
  Python Shell, prompt 2, line 1
builtins.TypeError: 'list' object is not an iterator

在上面的例子中,当我们试图调用 list 的 next 方法时,我们收到了一个 TypeError ,并被告知 list 对象不是迭代器。


>>> iter(my_list)
 >>> list_iterator = iter(my_list)
>>> next(list_iterator)
1
>>> next(list_iterator)
2
>>> next(list_iterator)
3
>>> next(list_iterator)
Traceback (most recent call last):
  Python Shell, prompt 8, line 1
builtins.StopIteration: 

要将列表转换成迭代器,只需将其封装在对 Python 的 iter 方法的调用中。然后你可以调用的下一个,直到迭代器用完所有条目,并且的 StopIteration 被抛出。让我们试着把这个列表变成一个迭代器,并用一个循环对它进行迭代:


>>> for item in iter(my_list):
...     print(item)
... 
1
2
3

当使用循环迭代迭代器时,不需要调用 next也不必担心会引发 StopIteration 异常。


创建你自己的迭代器

偶尔你会想要创建你自己的自定义迭代器。Python 让这变得非常容易。如前一节所述,您需要做的就是在您的类中实现 iternext 方法。让我们创建一个迭代器,它可以迭代一串字母:


class MyIterator:

    def __init__(self, letters):
        """
        Constructor
        """
        self.letters = letters
        self.position = 0

    def __iter__(self):
        """
        Returns itself as an iterator
        """
        return self

    def __next__(self):
        """
        Returns the next letter in the sequence or 
        raises StopIteration
        """
        if self.position >= len(self.letters):
            raise StopIteration
        letter = self.letters[self.position]
        self.position += 1
        return letter

if __name__ == '__main__':
    i = MyIterator('abcd')
    for item in i:
        print(item)

对于这个例子,我们的类中只需要三个方法。在我们的初始化中,我们传入字母字符串并创建一个类变量来引用它们。我们还初始化了一个位置变量,这样我们总是知道我们在字符串中的位置。iter 方法只返回它自己,这是它真正需要做的。next 方法是这个类中最重要的部分。在这里,我们根据字符串的长度检查位置,如果我们试图超过它的长度,就引发 StopIteration。否则我们提取我们所在的字母增加位置并返回字母。

让我们花点时间来创建一个无限迭代器。无限迭代器是可以永远迭代的迭代器。在调用这些函数时你需要小心,因为如果你不确定给它们加一个界限,它们会导致一个无限循环。


class Doubler:
    """
    An infinite iterator
    """

    def __init__(self):
        """
        Constructor
        """
        self.number = 0

    def __iter__(self):
        """
        Returns itself as an iterator
        """
        return self

    def __next__(self):
        """
        Doubles the number each time next is called
        and returns it. 
        """
        self.number += 1
        return self.number * self.number

if __name__ == '__main__':
    doubler = Doubler()
    count = 0

    for number in doubler:
        print(number)
        if count > 5:
            break
        count += 1         

在这段代码中,我们不向迭代器传递任何东西。我们只是实例化它。然后,为了确保我们不会陷入无限循环,我们在开始迭代自定义迭代器之前添加了一个计数器。最后,当计数器超过 5 时,我们开始迭代并中断。


发电机

一个普通的 Python 函数将总是返回一个值,无论它是一个列表、一个整数还是一些其他对象。但是,如果您希望能够调用一个函数并让它产生一系列值,该怎么办呢?这就是发电机的用武之地。生成器的工作原理是“保存”它最后停止的地方(或产出),并给调用函数一个值。因此,它不是将执行返回给调用者,而是将临时控制权交还给调用者。要做到这一点,生成器函数需要 Python 的 yield 语句。

附注:在其他语言中,生成器可能被称为协程。

让我们花点时间创建一个简单的生成器!


>>> def doubler_generator():
...     number = 2
...     while True:
...         yield number
...         number *= number
>>> doubler = doubler_generator()
>>> next(doubler)
2
>>> next(doubler)
4
>>> next(doubler)
16
>>> type(doubler)

这个特殊的生成器基本上会创建一个无限序列。你可以整天在它上面调用下一个,它将永远不会失去价值。因为可以在生成器上迭代,所以生成器被认为是迭代器的一种,但是没有人真正这样称呼它们。但是在幕后,生成器也定义了我们在上一节中看到的 next 方法,这就是为什么我们刚刚使用的 next 关键字有效。

让我们看看另一个例子,它只产生 3 项,而不是一个无限序列!


>>> def silly_generator():
...     yield "Python"
...     yield "Rocks"
...     yield "So do you!"
>>> gen = silly_generator()
>>> next(gen)
'Python'
>>> next(gen)
'Rocks'
>>> next(gen)
'So do you!'
>>> next(gen)
Traceback (most recent call last):
  Python Shell, prompt 21, line 1
builtins.StopIteration:

这里我们有一个生成器,它使用了 3 次 yield 语句。在每种情况下,它都会产生不同的字符串。你可以把收益率想象成一个发电机的收益语句。无论何时调用 yield该函数都会停止并保存其状态。然后它输出值 out这就是为什么在上面的例子中你会看到一些东西被输出到终端。如果我们的函数中有变量这些变量也会被保存。

当你看到 StopIteration 的时候,你就知道你已经穷尽了迭代器。这意味着它用完了项目。这是所有迭代器的正常行为,正如你在迭代器部分看到的一样。

无论如何,当我们再次调用 next 时,生成器从它停止的地方开始,产生下一个值,或者我们完成函数,生成器停止。另一方面,如果你不再调用 next那么这个状态最终会消失。

让我们重新实例化生成器,并尝试遍历它!


>>> gen = silly_generator()
>>> for item in gen:
...     print(item)
... 
Python
Rocks
So do you!

我们创建生成器的一个新实例的原因是,如果我们试图对它进行循环,将不会产生任何结果。这是因为我们已经遍历了生成器的特定实例中的所有值。因此,在本例中,我们创建了新的实例,对其进行循环,并打印出生成的值。循环的再次为我们处理 StopIteration 异常,并在生成器耗尽时退出循环。

生成器的最大好处之一是它可以迭代大型数据集,并一次返回一部分。当我们打开一个文件并逐行返回时,就会发生这种情况:


with open('/path/to/file.txt') as fobj:
    for line in fobj:
        # process the line

当我们以这种方式迭代文件对象时Python 基本上将它变成了一个生成器。这允许我们处理太大而无法加载到内存中的文件。您会发现生成器对于您需要成块处理的任何大型数据集都很有用,或者当您需要生成一个大型数据集,否则它会填满您所有的计算机内存。


包扎

至此,你应该明白什么是迭代器以及如何使用迭代器。您还应该知道 iterable 和 iterator 之间的区别。最后,我们学习了什么是发电机,以及为什么你可能想要使用发电机。例如,生成器非常适合内存高效的数据处理。编码快乐!