《流畅的Python》读书笔记16(第十四章:可迭代的对象、迭代器和生成器)

255 阅读5分钟

在Python中,所有集合都可以爹迭代。在Python语言内部,迭代器用于支持:

  • for循环
  • 构建和扩展集合类型
  • 逐行遍历文本文件
  • 列表推导、字典推导和集合推导
  • 元组拆包
  • 调用函数时,使用*拆包实参

14.1 Sentence第一版:单词序列

我们要实现一个Sentence类,以此打开探索可迭代对象的旅程。我们向这个类的构造方法传入包含一些文本的字符串,然后可以逐个单词迭代。

示例14-1 sentence.py:把句子划分为单词序列

import re
import reprlib
RE_WORD = re.compile('\w+')


class Sentence:
    def __init__(self, text):
        self.text = text
        self.words = RE_WORD.findall(text)

    def __getitem__(self, index):
        return self.words[index]

    def __len__(self):
        return len(self.words)

    def __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)

示例14-2 测试Sentence实例能否迭代

from sentence import Sentence
s = Sentence('"The time has come," the Walrus said,')
s
Sentence('"The time ha... Walrus said,')
for word in s:
    print(word)
    
The
time
has
come
the
Walrus
said

序列可以迭代的原因:iter函数 解释器需要迭代对象x时,会自动调用iter(x)

内置的iter函数有以下作用。

  1. 检查对象是否实现了__iter__方法,如果实现了就调用它,获取一个迭代器。
  2. 如果没有实现__iter__方法,但是实现了__getitem__方法,Python会创建一个迭代器,尝试按顺序获取元素。
  3. 如果尝试失败,Python会抛出TypeError异常,通常会提示:“C object is not iterable”,其中C是目标对象所属的类。

任何Python序列都可迭代的原因是,它们都实现了__getitem__方法。

14.2 可迭代的对象与迭代器的对比

可迭代的对象和迭代器之间的关系:Python从可迭代的对象中获取迭代器。

for循环,迭代一个字符串。

>>> for char in s :

...     print(char)

... 

A

B

C

如果没有for语句,不得不使用while循环模拟,要像下面这样写:

>>> s = "ABC"

>>> it = iter(s)

>>> while True:

...     try:

...         print(next(it))

...     except StopIteration:

...         del it

...         break

... 

A

B

C

标准的迭代器接口有两个方法:

__next__

返回下一个可用的元素,如果没有元素了,抛出StopIteration异常。

__iter__

返回self,一遍在英国使用可迭代对象的地方使用迭代器,例如在for循环中。

image.png 图14-1:Iterable和Iterator抽象基类

迭代Sentece类

s3 = Sentence('Pig and Pepper')
it = iter(s3)
it
<iterator object at 0x7fda48143828>
next(it)
'Pig'
next(it)
'and'
next(it)
'Pepper'
next(it)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
StopIteration
list(it)
[]
list(iter(s3))
['Pig', 'and', 'Pepper']

迭代器

迭代器是这样的对象,实现了无参数的__next__方法,返回序列中的下一个元素;如果没有元素了,那么抛出StopIteration异常。Python中的迭代器还实现了__iter__方法,因此迭代器也可以迭代。

14.3 Sentence类第2版:典型的迭代器

第2版Sentence根据《设计模式》艺术给出的模型,实现了典型的迭代器设计模式。注意,这不符合Python的习惯做法。

之所以这么做,是为了清楚地说明可迭代的对象和迭代器之间的重要区别,以及二者之间的联系。

示例14-4 sentence_iter.py:使用迭代器模式实现Sentence类

import re
import reprlib

RE_WORD = re.compile('\w+')


class Sentence:

    def __init__(self, text):
        self.text = text
        self.words = RE_WORD.findall(text)

    def __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)

    def __iter__(self):
        return SentenceIterator(self.words)


class SentenceIterator:

    def __init__(self, words):
        self.words = words
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        try:
            word = self.words[self.index]
        except IndexError:
            raise StopIteration
        self.index += 1
        return word

把Sentence变成迭代器:坏主意

构建可迭代的对象和迭代器时经常会出现错误,原因是混淆了二者。要知道,可迭代的对象有个__iter__方法,每次都实例化一个新的迭代器;而迭代器要实现__next__方法,返回单个元素,此外还要实现__iter__方法,返回迭代器本身。

14.4 Sentence类第三版:生成器函数

实现相同的功能,但却符合Python习惯的方式是,用生成器函数代替SentenceIterator类。

示例14-5 sentence_gen.py:使用生成器函数实现Sentence类

import re
import reprlib

RE_WORD = re.compile('\w+')

class Sentence:

    def __init__(self, text):
        self.text = text
        self.words = RE_WORD.findall(text)

    def __iter__(self):
        for word in self.words:
            yield word
        return

    def __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)


s3 = Sentence("Pig and Pepper")
it = iter(s3)
print(type(it))
print(next(it))
print(next(it))
print(next(it))
<class 'generator'>
Pig
and
Pepper

生成器的工作原理 只要Python函数的定义体中有yield关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。也就是说,生成器函数是生成器工厂。

下面以一个简单的函数说明生成器的行为:

>>> def gen_123():
...     yield 1
...     yield 2
...     yield 3
...
>>> gen_123
<function gen_123 at 0x000002390082C268>
>>> gen_123()
<generator object gen_123 at 0x0000023900851DE0>
>>> for i in gen_123():
...     print(i)
...
1
2
3
>>> g = gen_123()
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
Traceback (most recent call last):
  ...
StopIteration

示例14-6 运行时打印消息的生成器函数

>>> def gen_AB():
...     print('start')
...     yield 'A'
...     print('continue')
...     yield('B')
...     print('end')
...
>>> for c in gen_AB():
...     print('-->', c)
...
start
--> A
continue
--> B
end

14.5 Sentence类第4版:惰性实现

设计Iterator接口时考虑到了惰性:next(my_iterator)一次生成一个元素。

目前实现的几版Sentence类都不具有惰性,因为__init__方法急迫地构建好了文本中的单词列表,然后将其绑定到self.words属性上。

示例14-7 sentence_gen2.py:在生成器函数中调用re.finditer生成器函数

import re
import reprlib

RE_WORD = re.compile('\w+')


class Sentence:

    def __init__(self, text):
        self.text = text

    def __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)

    def __iter__(self):
        for match in RE_WORD.finditer(self.text):
            yield match.group()

14.6 Sentence类第5版:生成器表达式

生成器表达式可以理解为列表推导的惰性版本:不会迫切地构建列表,而是返回一个生成器,按需惰性生成元素。

示例14-8 对比列表推导与生成器表达式

>>> def gen_AB():
...     print('start')
...     yield 'A'
...     print('continue')
...     yield 'B'
...     print('end')
...
>>> res1 = [x * 3 for x in gen_AB()]
start
continue
end
>>> res1
['AAA', 'BBB']
>>> res2 = (x*3 for x in gen_AB())
>>> res2
<generator object <genexpr> at 0x0000023900B44570>
>>> for i in res2:
...     print('-->', i)
...
start
--> AAA
continue
--> BBB
end

示例14-9 sentence_genxp.py:使用生成器表达式实现Sentence类

import re
import reprlib

RE_WORD = re.compile('\w+')


class Sentence:

    def __init__(self, text):
        self.text = text

    def __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)

    def __iter__(self):
        return (match.group() for match in RE_WORD.finditer(self.text))

生成器表达式是语法糖:完全可以替换生成器函数。

14.7 何时使用生成器表达式

如果生成器表达式要分成多行写,倾向定义生成器函数,以便提高可读性。此外,生成器函数有名称,因此可以重用。