geekdoc-python-zh/docs/pythonland/37.md

236 lines
9.2 KiB
Markdown
Raw Permalink Normal View History

2024-03-03 22:54:39 +08:00
# Python 迭代器:示例代码及其工作原理
> 原文:[https://python.land/deep-dives/python-iterator](https://python.land/deep-dives/python-iterator)
要理解什么是 Python 迭代器,您需要知道两个术语:迭代器和可迭代的:
Iterator
An object that can be iterated, meaning we can keep asking it for a new element until there are no elements left. Elements are requested using a method called `__next__`.
Iterable
An object that implements another special method, called __iter__. This [function](https://python.land/introduction-to-python/functions) returns an iterator.
多亏了迭代器Python 有了非常优雅的 for 循环。迭代器也使理解成为可能。尽管理解所有的内部工作需要一些工作,但它们在实践中非常容易使用!
目录
* [Python 迭代器如何工作](#How_a_Python_iterator_works "How a Python iterator works")
* [为什么迭代器和可迭代对象是分开的对象?](#Why_are_iterators_and_iterables_separate_objects "Why are iterators and iterables separate objects?")
* [内置 Python 迭代器](#Built-in_Python_iterators "Built-in Python iterators")
* [如何使用 Python 迭代器](#How_to_use_a_Python_iterator "How to use a Python iterator")
* [创建自己的 Python 迭代器](#Creating_your_own_Python_iterator "Creating your own Python iterator")
## Python 迭代器如何工作
如上所述Python 迭代器对象实现了一个函数,该函数需要携带确切的名称`__next__`。这个特殊函数一直返回元素,直到它用完了要返回的元素,在这种情况下,类型为`StopIteration`的[异常](https://python.land/deep-dives/python-try-except)被引发。要获得一个迭代器对象,我们需要首先在一个可迭代对象上调用`__iter__`方法。
我们可以使用内置的 [Python range 函数](https://python.land/deep-dives/python-range)看到这一切,这是一个内置的 Python iterable。让我们做一个小实验:
```py
>>> my_iterable = range(1, 3)
>>> my_iterator = my_iterable.__iter__()
>>> my_iterator.__next__()
1
>>> my_iterator.__next__()
2
>>> my_iterator.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
```
如你所见:
* range 返回一个 iterable 对象,因为它有`__iter__`方法。
* 我们调用函数并将它返回的迭代器赋给`my_iterator`。
* 接下来,我们开始重复调用`__next__`方法,直到到达范围的末尾并引发`StopIteration`异常。
当然,这不是你在实践中使用迭代器的方式。我只是在演示它们内部是如何工作的。如果您需要手动获取迭代器,请使用`iter()`函数。而如果需要手动调用`__next__`方法,可以使用 Python 的`next()`函数。
因为只有下一个函数,你只能用迭代器前进。没有办法重置迭代器(除了创建一个新的迭代器)或获取以前的元素。
## 为什么迭代器和可迭代对象是分开的对象?
迭代器和可迭代对象可以是独立的对象,但不是必须的。没有什么能阻止我们回到这里。如果愿意,可以创建一个既是迭代器又是可迭代的对象。你只需要同时实现`__iter__`和`__next__`。
那么,为什么构建这种语言的聪明人决定拆分这些概念呢?这与保持状态有关。迭代器需要维护位置信息,例如指向内部数据对象(如列表)的指针。换句话说:它必须跟踪下一个要返回的元素。
如果 iterable 本身保持这种状态,您一次只能在一个循环中使用它。否则,其他循环会干扰第一个循环的状态。通过返回一个新的迭代器对象和它自己的状态,我们没有这个问题。这很方便,特别是当你使用并发性的时候。
## 内置 Python 迭代器
一旦你开始寻找,你会发现许多 Python 类型是可迭代的。以下是 Python 固有的一些可迭代类型:
* [Python 列表](https://python.land/python-data-types/python-list)
* [Python 集](https://python.land/python-data-types/python-set-the-why-and-how-with-example-code)
* [Python 词典](https://python.land/python-data-types/dictionaries)
* [Python 元组](https://python.land/python-data-types/python-tuple)
* [范围](https://python.land/deep-dives/python-range)
* [琴弦](https://python.land/introduction-to-python/strings)
还有一些被称为生成器的特殊类型的迭代。最突出的生成器示例是 range()函数,它返回指定范围内的项目。这个函数生成这些数字,而不需要在一个实际的列表中具体化它们。显而易见的优点是,它可以节省大量内存,因为它需要做的只是记录上一次迭代的次数,以计算下一项。
Thank you for reading my tutorials. I write these in my free time, and it requires a lot of time and effort. I use ads to keep writing these *free* articles, I hope you understand! **Support me by disabling your adblocker on my website** or, alternatively, **[buy me some coffee](https://www.buymeacoffee.com/pythonland)**. It's much appreciated and allows me to keep working on this site!
## 如何使用 Python 迭代器
现在我们了解了迭代器的工作原理,让我们看看如何使用 Python 迭代器。你会很快发现它的感觉和外观都很自然,一点也不难!
### for 循环中的迭代器
与其他编程语言不同,[循环](https://python.land/introduction-to-python/python-for-loop) *要求*可迭代。下面是我们迭代一个列表和一个[字符串](https://python.land/introduction-to-python/strings)的两个例子:
```py
>>> mystring = "ABC"
>>> for letter in mystring:
... print(letter)
...
A
B
C
>>> mylist = ['A', 'B', 'C']
>>> for letter in mylist:
... print(letter)
...
A
B
C
```
如您所见就可迭代性而言Python 字符串的行为与 Python 列表相同。
### 理解中的迭代器
就像 for 循环一样,[理解](https://python.land/deep-dives/list-comprehension)也需要一个可迭代的对象:
```py
>>> [x for x in 'ABC']
['A', 'B', 'C']
>>> [x for x in [1, 2, 3,4] if x > 2]
[3, 4]
>>>
```
### 迭代 Python 字典键
Python 字典是可迭代的,因此我们可以循环遍历字典的所有键。字典迭代器只返回键,不返回值。这里有一个例子:
```py
>>> d = {'name': 'Alice', 'age': 23, 'country': 'NL' }
>>> for k in d:
... print(k)
...
name
age
country
```
有时候看到有人用这个来代替:`for k in d.keys()`。虽然结果一样,但明显少了几分优雅。
### 迭代字典值
要迭代 [Python 字典](https://python.land/python-datatypes/dictionaries)的值,可以使用 values()方法:
```py
>>> for k in d.values():
... print(k)
...
Alice
23
NL
```
### 迭代字典键*和*值
如果您想从字典中获得键和值,请使用`items()`方法。您可以将`items()`与 [for 循环](https://python.land/introduction-to-python/python-for-loop)或 [Python 列表理解](https://python.land/deep-dives/list-comprehension)一起使用:
```py
>>> for k,v in d.items():
... print(k, v)
...
name Alice
age 23
country NL
>>>
>>> # With a list comprehension and f-string
>>> [f'{k}: {v}' for k, v in d.items()]
['name: Alice', 'age: 23', 'country: NL']
```
### 将 Python 迭代器转换为列表、元组、字典或集合
可以使用`list()`函数将迭代器具体化为一个列表。同样,您可以使用`set()`函数将迭代器具体化为一个集合,或者使用`tuple()`函数将其具体化为一个元组:
```py
>>> list(range(1, 4))
[1, 2, 3]
>>> set(range(1, 4))
{1, 2, 3}
>>> tuple(range(1, 4))
(1, 2, 3)
```
如果您有一个返回(键,值)元组的迭代器,您可以用`dict()`将其具体化。
### 用 Python 逐行读取文件
多亏了迭代器,用 Python 逐行读取文件非常容易:
```py
with open('cities.txt') as cities:
for line in cities:
proccess_city(line)
```
open()函数返回一个 iterable 对象,可以在 for 循环中使用。如果你喜欢,可以阅读我的综合文章,其中有关于用 Python 处理文件的所有细节。
## 创建自己的 Python 迭代器
创建自己的迭代器没有什么魔力。我将用一个返回偶数的简单迭代器类来演示。
正如我们所了解的,我们需要实现 __iter____next__。为了简单起见,我们将在一个单独的类中做这件事。我们用一个 __iter__ 方法来完成这个任务,该方法只返回`self`。我们可以把它变成一个无限的迭代器。但是为了便于演示,我们将在通过数字 8 时引发一个`StopIteration`异常。
请记住,如果您以这种方式构建迭代器,您就不能在并发环境中使用它。在这种情况下,您应该在每次调用`__iter__`时返回一个新对象。
如果你需要复习,请先阅读我们关于[类和对象](https://python.land/objects-and-classes)的教程。
```py
class EvenNumbers:
last = 0
def __iter__(self):
return self
def __next__(self):
self.last += 2
if self.last > 8:
raise StopIteration
return self.last
even_numbers = EvenNumbers()
for num in even_numbers:
print(num)
```
如果运行这个程序,输出将是:
```py
2
4
6
8
```
如果您愿意,可以交互式地玩这个例子:
[https://crumb . sh/embed/vywqnj 476 au](https://crumb.sh/embed/vywqnj476Au)