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

本篇文章简单谈谈可迭代对象,迭代器和生成器之间的关系。
首先用一张图直观的感受下三者以及序列、字典简单关系。

序列与迭代

从关系图中我们可以看出序列也是可迭代对象,但是在迭代的分类上序列与迭代器是一个级别的,所以序列实现迭代的方式一定是区别于迭代器的,下面讲述原因。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import re
import reprlib

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

class Sentence:
'''first edition of 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)

迭代对象时的步骤:

  1. 调用内置的iter()函数。
  2. 检查对象是否实现了__iter__方法,如果实现了则调用获取一个迭代器。
  3. 如果没有实现__iter__方法,则会检查是否实现了__getitem__方法,如果实现了该方法并且索引是从0开始的,Python会自动创建一个迭代器。
  4. 如果二者都没有实现,Python会抛出TypeError异常。

鸭子与白鹅

  • 鸭子:实现__iter____getitem__两个方法,并且后者的参数从0开始,才认为是可以迭代的。
  • 白鹅:实现__iter__方法就是可以迭代的。可以通过issubclass(C, abc.Iterable)的测试。

可迭代对象与迭代器

关系

  • 可迭代对象包含迭代器。
  • 如果一个对象拥有__iter__方法,其是可迭代对象,如果一个对象拥有__next__方法,其是迭代器。
  • 可迭代对象必须实现__iter__方法,迭代器必须实现__iter____next__方法。

两个抽象基类

Iterable.__iter__方法返回一个Iterator实例。Iterator.__iter__方法返回实例本身,并且具体的Iterator类必须实现__next__方法。

得益于Iterator.__subclasshook__方法,我们可以使用issubclass(C, abc.Iterable)检查对象C是否为迭代器,这样即使对象C所属的类不是Iterator的真实子类或虚拟子类也可以检查。

__next__

  • __next__方法不接受参数,所以只能执行一种操作–取下一个元素(当没有下一个元素是抛出StopIteration异常)。
  • __next__的特点导致了迭代器是一次性用品,无法还原。
  • next()函数调用__next__方法。

自定义迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/usr/bin/env python
# coding=utf-8

import re
import reprlib

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

class Sentence: # 可迭代对象
'''second edition of 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 __next__(self):
try:
word = self.words[self.index]
except IndexError:
raise StopIteration()
self.index += 1
return word

def __iter__(self): # 迭代器返回自身
return self

Sentence是一个可迭代对象(类)(实现了__iter__方法),SentenceIterator是一个迭代器(实现了__iter____next__方法)。

迭代器模式的作用:

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

迭代器模式的要求:

为了“支持多种遍历”,必须能从同一个可迭代的实力中获取多个独立的迭代器。而且每个迭代器要能维护自身的内部状态,所以这一模式的正确实现方式是,每次调用iter(C)新建一个独立的迭代器。所以上述代码中Sentence.__iter__方法返回一个迭代器SentenceIterator,也正印证了这里所说。

可迭代对象一定不能是自身的迭代器(迭代器的__iter__方法要返回自己,这样就没法维持对象自身状态不变)。所以可迭代对象必须实现__iter__方法,但是不能实现__next__方法。

生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import re
import reprlib

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

class Sentence: //生成器
'''third edition of 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

生成器函数

1
2
3
4
5
6
7
8
def aritprog_gen(begin, step, end = None):
result = type(begin + step)(begin)
forever = end is None
index = 0
while forever or result < end:
yield result
index += 1
result = begin + step * index

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

惰性求值与及早求值

生成器表达式

生成器表达式是语法糖完全可以代替生成器函数,而且有时使用生成器表达式更便利。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import re
import reprlib

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

class Sentence:
'''fifth edition of 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))

生成器作为协程使用

16章讲,作者特意分开了。

yield from

在生成器函数中,yield from可以代替一层for循环用于产生元素。另外yield from函数可以创建通道,把内层生成器直接与外层生成器的客户端联系起来,把生成器当作协程使用。

1
2
3
def chain(*iterables):
for i in iterables:
yield from i

iter()函数

可以传入两个参数,使用常规的函数或任何可调用的对象创建迭代器,其中第一个参数(可调用对象)在没有参数时可以不断被调用并产生新的值。第二个参数是哨符,当可调用的对象返回这个值时,出发迭代器抛出StopIteration异常,而不产出哨符。

1
2
3
4
5
6
7
8
9
10
from random import randint

def d6():
return randint(1, 6)

d6_iter = iter(d6, 6)
for roll in d6_iter:
print(roll)

//d6_iter_false = iter(d6)

标准库中的生成器函数

  • 用于过滤的生成器函数
  • 用于映射的生成器函数
  • 合并多个可迭代对象的生成器函数
  • 把输入的各个元素扩展成多个输出的生成器函数
  • 用于重新排列元素的生成器函数
  • 可迭代的归约函数

把生成器当成协程

__next()__方法一样.send()方法致使生成器进入下一个yield语句,但是.send()方法允许使用生成器的客户端把数据传给自己,实现数据的双向交换。

本文标题:可迭代的对象、迭代器和生成器

文章作者:Darren

发布时间:2018年10月31日 - 16:10

最后更新:2018年11月01日 - 22:11

原始链接:http://Darren2017.github.io/2018/10/31/可迭代的对象、迭代器和生成器/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。