《Python进阶》读书笔记1-迭代器

《Python进阶》是《Intermediate Python》的中文版,几个月前在 V2EX 上偶然看到,瞄了几眼发现这本书每一章都很简短清晰,适合碎片时间阅读,就放到了收藏夹了。大体上说这本书是 tips 类型的,不适合新手入门看,至少需要了解、实践过了 Python 的常用语法才会看得比较流畅。我读完所有内容后又过了第二遍,把一些我觉得有意思的、不太熟悉的内容摘出来记一下。

iterator, generator, enumerate

迭代器(iterator)

文档:

An object representing a stream of data. Repeated calls to the iterator’s next() method return successive items in the stream. When no more data are available a StopIteration exception is raised instead. At this point, the iterator object is exhausted and any further calls to its next() method just raise StopIteration again. Iterators are required to have an iter() method that returns the iterator object itself so every iterator is also iterable and may be used in most places where other iterables are accepted. One notable exception is code which attempts multiple iteration passes. A container object (such as a list) produces a fresh new iterator each time you pass it to the iter() function or use it in a for loop. Attempting this with an iterator will just return the same exhausted iterator object used in the previous iteration pass, making it appear like an empty container.

首先说迭代器协议(iterator protocol):
iterator.__iter__()
iterator.next()
一个对象支持用 __iter__() 返回一个迭代器,同时又可以多次使用 next()方法(Python 2)来获取连续的值,直到没有值了,抛出 StopIteration 异常。

满足迭代器协议的对象就是一个迭代器。迭代器自己的 __iter__() 方法就直接返回 self。

接着可以明确什么是可迭代(iterable)对象:
Python中任意的对象,只要它定义了可以返回一个迭代器的 __iter__ 方法,或者定义了可以支持下标索引的 __getitem__ 方法,那么它就是一个可迭代对象。简单说,可迭代对象就是能提供迭代器的任意对象。
将可迭代对象传入 iter() 中就能返回一个迭代器对象。

支持 __iter__ 的对象通常是 list, tuple, dictionary, set 等这样的内置 container,以及迭代器对象本身。
字符串对象也是可迭代对象,如果你知道字符串也是一种 container,这就不难理解了。只是字符串对象没有 __iter__ 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> temp_list = ['a', 'b', 'c']
>>> temp_str = 'abcdef'

>>> iterator = iter(temp_list)
>>> iterator
<listiterator object at 0x101bd18d0>
>>> iterator.__iter__()
<listiterator object at 0x101bd18d0>
>>> iter(temp_str)
<iterator object at 0x101bd1910>

>>> temp_list.__iter__()
<listiterator object at 0x101bd1910>
>>> temp_list.__getitem__(0)
'a'

>>> temp_str.__iter()__
File "<input>", line 1
temp_str.__iter()__
^
SyntaxError: invalid syntax

>>> temp_str.__getitem__(0)
'a'

迭代(iteration)就是从迭代器里取值的过程,最常见的是 for 循环。

生成器(generator)

生成器(generator)也是一种迭代器,但是你只能对其迭代一次。这是因为它们并没有把所有的值存在内存中,而是在运行时生成值(懒加载的概念)。可以用一个 for 循环遍历来使用它们,或者将它们传递给任意可以进行迭代的函数和结构。

我觉得「只能对其迭代一次」这个说法不太好,普通的迭代器也只能用 .next() 迭代一次,不特殊处理的话,迭代完了你还是需要重新生成一个迭代器。生成器和普通迭代器最大的区别是在值的产生方式上,你不知道也不需要关心生成器里是用什么方式 yield 出来一个值的,你只知道你可以去一次次拿这个值直到拿完,而对普通的迭代器你知道它所有提供的值早就已经包含在迭代器对象中,在内存里了。

一篇博客中找到了张很清晰的关系图(这篇博客的内容本身也非常清晰详细)

关系图

enumerate

最后提一下 enumerate,只要知道迭代是什么意思,enumerate 的用法就很清晰了,先看文档:

enumerate(sequence, start=0)
Return an enumerate object. sequence must be a sequence, an iterator, or some other object which supports iteration. The next() method of the iterator returned by enumerate() returns a tuple containing a count (from start which defaults to 0) and the values obtained from iterating over sequence:

1
2
3
4
5
>>> seasons = ['Spring', 'Summer', 'Fall', 'Winter']
>>> list(enumerate(seasons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
>>> list(enumerate(seasons, start=1))
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]

Equivalent to:

1
2
3
4
5
def enumerate(sequence, start=0):
n = start
for elem in sequence:
yield n, elem
n += 1

一句话概括就是遍历数据并自动计数,这个计数的起始值可以设定。