236 lines
9.2 KiB
Markdown
236 lines
9.2 KiB
Markdown
|
|
# 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)
|