流畅的python14 章可迭代的对象迭代器 和生成器

Posted a3384451

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了流畅的python14 章可迭代的对象迭代器 和生成器相关的知识,希望对你有一定的参考价值。

可迭代的对象、迭代器和生成器

 

迭代是数据处理的基石。扫描内存中放不下的数据集时,我们要找到一种惰性获取数据项的方式,即按需一次获取一个数据项。这就是迭代器模式(Iterator pattern)。

迭代器用于从集合中取出元素;而生成器用于“凭空”生成元素。通过斐波纳契数列能很好地说明二者之间的区别:斐波纳契数
列中的数有无穷个,在一个集合里放不下。不过要知道,在 Python社区中,大多数时候都把迭代器和生成器视作同一概念。


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 __getitem__(self, index):
        return self.words[index] ?
    def __len__(self): ?
        return len(self.words)
    def __repr__(self):
        return Sentence(%s) % reprlib.repr(self.text) ?

?re.findall 函数返回一个字符串列表,里面的元素是正则表达式的
全部非重叠匹配。
? self.words 中保存的是 .findall 函数返回的结果,因此直接返回
指定索引位上的单词。
? 为了完善序列协议,我们实现了 __len__ 方法;不过,为了让对象
可以迭代,没必要实现这个方法。
? reprlib.repr 这个实用函数用于生成大型数据结构的简略字符串表
示形式。

示例 14-2 测试 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
>>> list(s) # ?
[The, time, has, come, the, Walrus, said]

? 传入一个字符串,创建一个 Sentence 实例。
? 注意,__repr__ 方法的输出中包含 reprlib.repr 方法生成的
...。
? Sentence 实例可以迭代,稍后说明原因。
? 因为可以迭代,所以 Sentence 对象可以用于构建列表和其他可迭代
的类型。

序列可以迭代的原因:iter函数

解释器需要迭代对象 x 时,会自动调用 iter(x)。内置的 iter 函数有以下作用。

  • (1) 检查对象是否实现了 __iter__ 方法,如果实现了就调用它,获取一个迭代器。
  • (2) 如果没有实现 __iter__ 方法,但是实现了 __getitem__ 方法,Python 会创建一个迭代器,尝试按顺序(从索引 0 开始)获取元素。
  • (3) 如果尝试失败,Python 抛出 TypeError 异常,通常会提示“C objectis not iterable”(C 对象不可迭代),其中 C 是目标对象所属的类。任何 Python 序列都可迭代的原因是,它们都实现了 __getitem__ 方法。其实,标准的序列也都实现了 __iter__ 方法,因此你也应该这么做。之所以对 __getitem__ 方法做特殊处理,是为了向后兼容,而未来可能不会再这么做

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

可迭代的对象
  使用 iter 内置函数可以获取迭代器的对象。如果对象实现了能返回迭代器的 __iter__ 方法,那么对象就是可迭代的。序列都可以迭代;实现了 __getitem__ 方法,而且其参数是从零开始的索引,这种
对象也可以迭代。


我们要明确可迭代的对象和迭代器之间的关系:Python 从可迭代的对象中获取迭代器。

下面是一个简单的 for 循环,迭代一个字符串。这里,字符串 ‘ABC‘是可迭代的对象。背后是有迭代器的,只不过我们看不到:

>>> s = ABC
>>> 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

? 使用可迭代的对象构建迭代器 it。
? 不断在迭代器上调用 next 函数,获取下一个字符。
? 如果没有字符了,迭代器会抛出 StopIteration 异常。
? 释放对 it 的引用,即废弃迭代器对象。
? 退出循环。

StopIteration 异常表明迭代器到头了。Python 语言内部会处理 for循环和其他迭代上下文(如列表推导、元组拆包,等等)中的StopIteration 异常。


标准的迭代器接口有两个方法。
__next__
  返回下一个可用的元素,如果没有元素了,抛出 StopIteration异常。
__iter__
  返回 self,以便在应该使用可迭代对象的地方使用迭代器,例如在 for 循环中。

迭代器

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

典型的迭代器

使用迭代器模式实现 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): #与前一版相比,这里只多了一个 __iter__ 方法。这一版没有
                           #__getitem__ 方法,为的是明确表明这个类可以迭代,因为实现了
                        #__iter__ 方法。
        return SentenceIterator(self.words) #根据可迭代协议,__iter__ 方法实例化并返回一个迭代器。
class SentenceIterator:
    def __init__(self, words):
        self.words = words #SentenceIterator 实例引用单词列表。
        self.index = 0#self.index 用于确定下一个要获取的单词。
    def __next__(self):
        try:
            word = self.words[self.index] #获取 self.index 索引位上的单词。
        except IndexError:
            raise StopIteration() #如果 self.index 索引位上没有单词,那么抛出 StopIteration 异
常。
        self.index += 1 
        return word 
    def __iter__(self): #实现 self.__iter__ 方法。
        return self            

生成器函数

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):
        for word in self.words: 
            yield word 
    return

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

惰性实现

目前实现的几版 Sentence 类都不具有惰性,因为 __init__ 方法急迫地构建好了文本中的单词列表,然后将其绑定到 self.words 属性上。
这样就得处理整个文本,列表使用的内存量可能与文本本身一样多(或许更多,这取决于文本中有多少非单词字符)。如果只需迭代前几个单词,大多数工作都是白费力气。
只要使用的是 Python 3,思索着做某件事有没有懒惰的方式,答案通常都是肯定的。

re.finditer 函数是 re.findall 函数的惰性版本,返回的不是列表,而是一个生成器,按需生成 re.MatchObject 实例。如果有很多匹配,re.finditer 函数能节省大量内存。我们要使用这个函数让第 4版 Sentence 类变得懒惰,即只在需要时才生成下一个单词。代码如示
例 14-7 所示。示例 14-7 sentence_gen2.py: 在生成器函数中调用 re.finditer生成器函数,实现 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):
        for match in RE_WORD.finditer(self.text): 
        yield match.group() 

? 不再需要 words 列表。
? finditer 函数构建一个迭代器,包含 self.text 中匹配 RE_WORD
的单词,产出 MatchObject 实例。
? match.group() 方法从 MatchObject 实例中提取匹配正则表达式的
具体文本。
生成器函数已经极大地简化了代码,但是使用生成器表达式甚至能把代
码变得更简短。

生成器表达式

生成器表达式可以理解为列表推导的惰性版本:不会迫切地构建列表,而是返回一个生成器,按需惰性生成元素。也就是说,如果列表推导是制造列表的工厂,那么生成器表达式就是制造生成器的工厂。

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

? gen_AB 函数与示例 14-6 中的一样。
? 列表推导迫切地迭代 gen_AB() 函数生成的生成器对象产出的元
素:‘A‘ 和 ‘B‘。注意,下面的输出是 start、continue 和 end.。
? 这个 for 循环迭代列表推导生成的 res1 列表。
? 把生成器表达式返回的值赋值给 res2。只需调用 gen_AB() 函数,
虽然调用时会返回一个生成器,但是这里并不使用。
? res2 是一个生成器对象。
? 只有 for 循环迭代 res2 时,gen_AB 函数的定义体才会真正执
行。for 循环每次迭代时会隐式调用 next(res2),前进到 gen_AB 函
数中的下一个 yield 语句。注意,gen_AB 函数的输出与 for 循环中
print 函数的输出夹杂在一起。

使用生成器表达式实现 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))

这里不是生成器函数了(没有 yield),而是使用生成器表达式构建生成器,然后将其返回。不过,最终的效果一样:调用 __iter__ 方法会得到一个生成器对象。
生成器表达式是语法糖:完全可以替换成生成器函数,不过有时使用生成器表达式更便利。

标准库中的生成器函数

用于过滤的生成器函数

模块 函数 说明
itertools

compress(it,
selector_it)

并行处理两个可迭代的对象;如果 selector_it
中的元素是真值,产出 it 中对应的元素

itertools

dropwhile(predicate,
it)

处理 it ,跳过 predicate 的计算结果为真值的元
素,然后产出剩下的各个元素(不再进一步检
查)

内置 filter(predicate, it)

把 it 中的各个元素传给 predicate ,如果
predicate(item) 返回真值,那么产出对应的元
素;如果 predicate 是 None ,那么只产出真值元

itertools

filterfalse(predicate,
it)

与 filter 函数的作用类似,不过 predicate 的
逻辑是相反的: predicate 返回假值时产出对应
的元素

itertools

islice(it, stop) 或
islice(it, start,
stop, step=1)

产出 it 的切片,作用类似于 s[:stop] 或
s[start:stop:step] ,不过 it 可以是任何可迭代
的对象,而且这个函数实现的是惰性操作

itertools

takewhile(predicate,
it)

predicate 返回真值时产出对应的元素,然后立
即停止,不再继续检查

 

以上是关于流畅的python14 章可迭代的对象迭代器 和生成器的主要内容,如果未能解决你的问题,请参考以下文章

第14章 可迭代的对象迭代器和生成器

Python要想学得好,容器/可迭代对象/迭代器/生成器少不了,稳扎稳打学Python!

Python要想学得好,容器/可迭代对象/迭代器/生成器少不了,稳扎稳打学Python!

python_函数进阶

python全栈开发14

流畅python学习笔记:第十四章:迭代器和生成器