《流畅的Python》8-可迭代对象,迭代器和生成器

关注的本节内容:

  • 实现一个可迭代对象和迭代器
  • 可迭代对象和迭代器的区分
  • 实现一个标准的迭代器
  • 生成器迭代器
  • (进阶)惰性定义一个Sentence
  • 生成器表达式(语法糖)

实现一个可迭代对象和迭代器

可迭代对象如何实现迭代?

调用iter(x) , x为可迭代对象。按照三个顺序:
1. 是否实现了__item__方法,如果实现了,调用他并获取一个迭代器
2. 是否实现了__getitem__方法,基于鸭子类型,这是为了向后兼容。
3. 抛出异常TypeError

显然,从鸭子类型duck type来说,标准的可迭代对象有__getitem____item__方法,所以实现一个可迭代对象也应该实现这两个抽象方法。

从白鹅类型goose type来说,考虑继承,实现__item__抽象方法即可。

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): # 实现__len__不是迭代必须的
        return len(self.words)

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

怎么看一个对象是否是迭代器

Python 3.4 开始,检查对象 x 能否迭代,最准确的方法是:
调用 iter(x) 函数,如果不可迭代,再处理 TypeError 异常。这
比使用 isinstance(x, abc.Iterable) 更准确,因为 iter(x)函数会考虑到遗留的 __getitem__ 方法,而 abc.Iterable 类则不考虑。

迭代器

定义

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

迭代器协议

迭代器实现两个接口:

  • __next__
  • __iter__ 方法实例化,并返回一个迭代器

迭代器的抽象基类是 collectons.abc.Iterator,和 collections.abc.Iterable
__next__抽象方法定义在Iterator中,__iter__方法定义在Iterable中,Iterable继承了Iterator

实现一个标准的迭代器

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

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

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


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

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

    def __iter__(self):
        return self

首先,需要确定这个典型的迭代器实现了哪些内容,符合了哪些标准?

  • __iter__方法返回了一个实例化的迭代器。后面讲到为什么要建一个新的SentenceIterator
  • SentenceIterator同时实现了__iter____next__。代码中也可以看出来,实现__iter__其实是不必要的,但他可以帮助通过迭代器的测试,包括isinstance(SentenceIterator,collectons.abc.IIterator)

为什么不直接在Sentence中实现__next__

这里可以回答上文中的为什么要有SentenceIterator的问题。

设计模式中(没看过)谈到迭代器模式:

  1. 访问一个聚合对象的内容而无需暴露它的内部表示
  2. 支持对聚合对象的多种遍历
  3. 为遍历不同的聚合结构提供一个统一的接口(即支持多态迭代)

其中,对聚合对象的多种遍历的含义就是,每次遍历都是相互独立,不受干扰的,结合 1,那么建一个新的SentenceIterator是最好的办法。

生成器迭代器

关键字yield 。函数内只要有yield,那么这个函数就是生成器函数。这个在讲生成器的技巧随处可见。
一般函数内会带个gen表明是生成器。

def gen_123():
  for i in range(5):
    yield 2*i

函数即对象
gen_123 是一个function
gen_123()是一个generator

改进代码,代替 SentenceIterator类:

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)

进阶优化,惰性定义Sentence类(利用现有迭代器)

惰性即lazy,他的反义词是急迫 eager
分为惰性求值 lazy evaluation和及早求值eager evaluation
所谓惰性求值,就是让__next__一次只生成一个,这才符合我们想要利用迭代器的根本目的——节约内存,而前面的版本在__init__的时候实际上已经生成全部数据。

书上指出

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

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

利用re库中的迭代器,
实际上暗示了要利用其他现有的迭代器来优化。

生成器表达式

实际上是一种语法糖,下面就是本节Sentence的最终版本:

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


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

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

总结

要分清可迭代对象和迭代器,迭代器是超集。标准迭代器必须要实现__iter____next____iter__返回的是一个迭代器的实例。学会使用yield生成器表达式来更像Python

测试的时候发现一个有趣的地方:

gen=(x*3 for x in range(5))
>>> gen
 at 0x7ff495a3d830>
>>> next(gen)
0
>>> next(gen)
3
>>> next(gen)
6
>>> list(gen)
[9, 12]
>>> next(gen)
Traceback (most recent call last):
  File "", line 1, in 
    next(gen)
StopIteration

实际上list的构造方法就是调用了迭代器,gen在构造的时候已经用的最后了。如果想要重复用,首先就应该

gen=(x*3 for x in range(5))
n=gen
开始调用next(n),gen作`原始数据`

你可能感兴趣的:(迭代器,生成器,迭代器模式,元组拆包,流畅的Python,※,Python,※,读书笔记,《流畅的Python》笔记)