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

240 lines
9.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Python 201:迭代器和生成器介绍
> 原文:<https://www.blog.pythonlibrary.org/2016/05/03/python-201-an-intro-to-iterators-and-generators/>
自从开始用 Python 编程以来,你可能一直在使用迭代器和生成器,但你可能没有意识到这一点。在这篇文章中,我们将学习什么是迭代器和生成器。我们也将学习它们是如何被创建的,这样我们就可以在需要的时候创建我们自己的。
### 迭代程序
迭代器是一个允许你遍历容器的对象。Python 中的迭代器通过两种不同的方法实现: **__iter__****__next__** 。您的容器需要 **__iter__** 方法来提供迭代支持。它将返回迭代器对象本身。但是如果您想要创建一个迭代器对象,那么您还需要定义 **__next__** ,这将返回容器中的下一个项目。
*注意:在 Python 2 中,命名约定略有不同。你仍然需要 **__iter__** ,但是 **__next__** 被称为 **next** 。*
为了让事情更加清楚,让我们回顾一下几个定义:
* iterable -定义了 __iter__ 方法的对象
* iterator -同时定义了 __iter____next__ 的对象,其中 __iter__ 将返回迭代器对象,而 __next__ 将返回迭代中的下一个元素。
与大多数神奇的方法(带有双下划线的方法)一样,您不应该直接调用 __iter____next__。相反,你可以使用一个**进行**循环或列表理解Python 会自动为你调用这些方法。在某些情况下,您可能需要调用它们,但是您可以使用 Python 的内置函数来这样做: **iter****next**
在我们继续之前我想提一下序列。Python 3 有列表、元组、范围等几种序列类型。该列表是可迭代的,但不是迭代器,因为它不实现 __next__。这在下面的例子中很容易看出:
```py
>>> 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 对象不是迭代器。
```py
>>> 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** 被抛出。让我们试着把这个列表变成一个迭代器,并用一个循环对它进行迭代:
```py
>>> for item in iter(my_list):
... print(item)
...
1
2
3
```
当使用循环迭代迭代器时,不需要调用 next也不必担心会引发 StopIteration 异常。
* * *
### 创建你自己的迭代器
偶尔你会想要创建你自己的自定义迭代器。Python 让这变得非常容易。如前一节所述,您需要做的就是在您的类中实现 __iter____next__ 方法。让我们创建一个迭代器,它可以迭代一串字母:
```py
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。否则我们提取我们所在的字母增加位置并返回字母。
让我们花点时间来创建一个无限迭代器。无限迭代器是可以永远迭代的迭代器。在调用这些函数时你需要小心,因为如果你不确定给它们加一个界限,它们会导致一个无限循环。
```py
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** 语句。
附注:在其他语言中,生成器可能被称为协程。
让我们花点时间创建一个简单的生成器!
```py
>>> 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 项,而不是一个无限序列!
```py
>>> 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那么这个状态最终会消失。
让我们重新实例化生成器,并尝试遍历它!
```py
>>> gen = silly_generator()
>>> for item in gen:
... print(item)
...
Python
Rocks
So do you!
```
我们创建生成器的一个新实例的原因是,如果我们试图对它进行循环,将不会产生任何结果。这是因为我们已经遍历了生成器的特定实例中的所有值。因此,在本例中,我们创建了新的实例,对其进行循环,并打印出生成的值。循环的**再次为我们处理 **StopIteration** 异常,并在生成器耗尽时退出循环。**
生成器的最大好处之一是它可以迭代大型数据集,并一次返回一部分。当我们打开一个文件并逐行返回时,就会发生这种情况:
```py
with open('/path/to/file.txt') as fobj:
for line in fobj:
# process the line
```
当我们以这种方式迭代文件对象时Python 基本上将它变成了一个生成器。这允许我们处理太大而无法加载到内存中的文件。您会发现生成器对于您需要成块处理的任何大型数据集都很有用,或者当您需要生成一个大型数据集,否则它会填满您所有的计算机内存。
* * *
### 包扎
至此,你应该明白什么是迭代器以及如何使用迭代器。您还应该知道 iterable 和 iterator 之间的区别。最后,我们学习了什么是发电机,以及为什么你可能想要使用发电机。例如,生成器非常适合内存高效的数据处理。编码快乐!