Python 进阶 迭代器


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

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

内存中的数据为啥还是要迭代?

所有生成器都是迭代器,因为生成器完全实现了迭代器接口。不过生成器可以生成数据源外的数据

1 Sentence类第1版:单词序列
# 第一版返回可迭代对象
class Sentence:
    def __init__(self, text):
        self.text = text
        self.words = RE_WORD.findall(text)  # 把句子划分为单词序列 re.findall 函数返回一个字符串列表,里面的元素是正则表达式的全部非重叠匹配
        # self.words 中保存的是 .findall 函数返回的结果,因此直接返回指定索引位上的单# 词

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

    # 实例可以迭代的原因:iter函数 等效 __getitem__  这里重置了他的返回值

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

    def __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)
        # reprlib.repr 这个实用函数用于生成大型数据结构的简略字符串表示形式
 s = Sentence('"The time has come," the Walrus said,')
# 验证是否可迭迭代对象 
try:
    iter(s)
except Exception as e:
    print("not iterable")

# 验证是否可迭迭代器 
try:
    next(iter(s))
    # 返回下一个可用的元素,如果没有元素了,抛出 StopIteration 异常。
except Exception as e:
    print("not Iterator")

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

解释器需要迭代对象 x 时,会自动调用 iter(x)。 内置的 iter 函数有以下作用。 (1) 检查对象是否实现了 iter 方法,如果实现了就调用它,获取一个迭代器。 (2) 如果没有实现 iter 方法,但是实现了 getitem 方法,Python 会创建一个迭 代器,尝试按顺序(从索引 0 开始)获取元素。 (3) 如果尝试失败,Python 抛出 TypeError 异常,通常会提示“C object is not iterable”(C 对象不可迭代),其中 C 是目标对象所属的类。

任何 Python 序列都可迭代的原因是,它们都实现了 getitem 方法,标准的序列也都实现了 iter 方法

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

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

迭代器 可使用next方法

3 Sentence类第2版:典型的迭代器
# 第二版返回迭代器
class Sentence2:
    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 __next__(self):
        try:
            word = self.words[self.index]
        except IndexError:
            raise StopIteration()
            self.index += 1
            return word

        def __iter__(self):
            return self

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

迭代器模式可用来: 访问一个聚合对象的内容而无需暴露它的内部表示 支持对聚合对象的多种遍历 为遍历不同的聚合结构提供一个统一的接口(即支持多态迭代) 为了“支持多种遍历”,必须能从同一个可迭代的实例中获取多个独立的迭代器,而且各个 迭代器要能维护自身的内部状态,因此这一模式正确的实现方式是,每次调用 iter(my_iterable) 都新建一个独立的迭代器。这就是为什么这个示例需要定义 SentenceIterator 类。

且迭代器没有len 方法

s = 'ABC'
>>> for char in s:
... print(char)

s = 'ABC'
>>> it = iter(s) # ➊
>>> while True:
... try:
... print(next(it)) # ➋
... except StopIteration: # ➌
... del it # ➍
... break #

所以for 的本质是while 语法糖
何使用
iter(...) 函数构建迭代器,以及如何使用 next(...) 函数使用迭代器:
4 Sentence类第3版:生成器函数

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

class Sentence3:
    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  # python 生成器关键字yield  作用等于SentenceIterator
        return

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

2 生成器函数的定义体中通常都有循环,不过这不是最核心的条件;

3 生成器是迭代器,会生成传给 yield 关键字的表达式的值。

4 函数返回值;调用生成器函数返回生成器;生成器产出或生成值

5 for 循环中 会隐式调用 next() 函数

5 Sentence 类第4版 :惰性实现

设计 Iterator 接口时考虑到了惰性:next(my_iterator) 一次生成一个元素。懒惰的 反义词是急迫,其实,惰性求值(lazy evaluation)和及早求值(eager evaluation)是编程 语言理论方面的技术术语。

在生成器函数中调用 re.finditer 生成器函数,实现Sentence 类

class Sentence4:  # 此时实例为一个生成器对象了 调用生成器对象需要继续使用for 触发yield 实现惰性生产数据
    def __init__(self, text):
        self.text = text  # 不再需要 words 列表。

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

    def __iter__(self):
        for match in RE_WORD.finditer(self.text):
            yield match.group()  # match.group() 方法从 MatchObject 实例中提取匹配正则表达式的具体文本。

s = Sentence4('"The time has come," the Walrus said,')
print(s)
for i in s:
    print(i)
6 Sentence类第5版:生成器表达式

生成器表达式可以理解为列表推导的惰性版本:

不会迫切地构建列表,而是返回一个生成器,按需惰性生成元素。

也就是说,如果列表推导是制造列表的工厂,那么生成器表达式就是制造生成器的工厂。

def gen_AB():
    print('start')
    yield 'A'
    print('continue')
    yield 'B'
    print('end.')


res1 = [x * 3 for x in gen_AB()]  # 列表推导式
print(res1)
for i in res1:
    print('-->', i)

res2 = (x * 3 for x in gen_AB())  # 生成器表达式 res 2 为一个生成器对象 只有for 时才能迭代出值
print(res2)
for i in res2:
    print('-->', i)

# 已经和asyc 很像了

sentence_genexp.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))

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

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

如果函数或构造方法只有一个参数,传入生成器表达式时不用写一对调用函数的括 号,再写一对括号围住生成器表达式,只写一对括号就行了,如示例 10-16 中 mul 方法对 Vector 构造方法的调用,转摘如下。然而,如果生成器表达式后面 还有其他参数,那么必须使用括号围住,否则会抛出 SyntaxError 异常:

def __mul__(self, scalar):
    if isinstance(scalar, numbers.Real):
        return Vector(n * scalar for n in self)
    else:
        return NotImplemented

目前所见的 Sentence 类示例说明了如何把生成器当作典型的迭代器使用,即从集合中获 取元素。不过,生成器也可用于生成不受数据源限制的值

8 另一个示例:等差数列生成器典

典型的迭代器模式作用很简单——遍历数据结构。不过,即便不是从集合中获取元素,而 是获取序列中即时生成的下一个值时,也用得到这种基于方法的标准接口。例如,内置的 range 函数用于生成有穷整数等差数列(Arithmetic Progression, AP),itertools.count 函数用于生成无穷等差数列。

9 标准库中的生成器函数

标准库提供了很多生成器,有用于逐行迭代纯文本文件的对象,还有出色的 os.walk 函 数

14 把生成器当成协程

与 .next() 方法一样,.send() 方法致使生成器前进到下一个 yield 语句。不 过,.send() 方法还允许使用生成器的客户把数据发给自己,即不管传给 .send() 方法 什么参数,那个参数都会成为生成器函数定义体中对应的 yield 表达式的值。也就是 说,.send() 方法允许在客户代码和生成器之间双向交换数据。而 .next() 方法只 允许客户从生成器中获取数据。

这是一项重要的“改进”,甚至改变了生成器的本性

生成器用于生成供迭代的数据 协程是数据的消费者 为了避免脑袋炸裂,不能把这两个概念混为一谈 协程与迭代无关 注意,虽然在协程中会使用 yield 产出值,但这与迭代无关