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

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

所有生成器都是迭代器,因为生成器完全实现了迭代器接口。迭代器用于从集合中取出元素;而生成器用于“凭空”生成元素。

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

import re
import reprlib

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


class Sentence(object):
    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)

s = Sentence('this time has come, we are ready')
print(s)
for i in s:
    print(i)

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

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

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

==使用 iter()内置函数可以获取迭代器的对象==。可迭代的对象和迭代器之间的关系: Python 从可迭代的对象中获取迭代器。

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

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

iter返回 self,以便在应该使用可迭代对象的地方使用迭代器,例如在 for 循环中。

s = Sentence('this time has come, we are ready')
it = iter(s)
print(next(it))
print(next(it))

典型的迭代器

可以在Sentence类中实现iter方法,并且返回一个迭代器SentenceIterator实例。而SentenceIterator类必须是一个迭代器,迭代器应该实现nextiter两个方法并且迭代器的iter需要返回self。

总结:
1. 可迭代的对象(Sentence的实例)一定不能是自身的迭代器。也就是说,可迭代的对象必须实现iter方法,但不能实现next方法。
2. 迭代器(SentenceIterator)应该一直可以迭代。迭代器的iter方法应该返回自身。

生成器函数

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

import re
import reprlib

RE_WORD = re.compile('\w+')
class Sentence(object):
    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


s = Sentence('this time has come, we are ready')
it = iter(s)
print(next(it))
print(next(it))

Sentence.iter方法的作用了:iter方法是生成器函数,调用时会构建一个实现了迭代器接口的生成器对象,因此不用再定义SentenceIterator 类了。

生成器函数的工作原理

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

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

惰性实现

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

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

==re.finditer 函数是 re.findall 函数的惰性版本,返回的不是列表,而是一个生成器==,按需生成 re.MatchObject 实例。如果有很多匹配, re.finditer 函数能节省大量内存。

RE_WORD = re.compile('\w+')
class Sentence(object):
    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()

生成器表达式

==简单的生成器函数可以替换成生成器表达式,生成器表达式使用括号生成()==。生成器表达式可以理解为列表推导的惰性版本。

a = (i for i in 'ABC')
print(a) # <generator object <genexpr> at 0x7f253342b7d8>

所以上面的iter还可以写成这样:

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

标准库中的生成器函数

用于过滤的生成器函数:

大多数函数都接受一个断言参数(predicate)。这个参数是个布尔函数,有一个参数,会应用到输入中的每个元素上,用于判断元素是否包含在输出中。

这里写图片描述

a = filter(lambda i: i > 3, [1, 2, 3, 4, 5])
for i in a:
    print(i) # 4, 5

用于映射的生成器函数

在输入的单个可迭代对象中的各个元素上做计算,然后返回结果。

这里写图片描述

import itertools
sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]
a = list(itertools.accumulate(sample))
# [5, 9, 11, 19, 26, 32, 35, 35, 44, 45]
print(a) 


# [(0, 2), (1, 4), (2, 8)]
list(map(lambda a, b: (a, b), range(11), [2, 4, 8]))

合并多个可迭代对象的生成器函数

这里写图片描述

a = list(itertools.chain('ABC', range(2)))
# ['A', 'B', 'C', 0, 1]
print(a)

b = list(zip('ABC', range(5)))
# [('A', 0), ('B', 1), ('C', 2)]
print(b)

c = list(itertools.zip_longest('ABC', range(5), fillvalue='?'))
# [('A', 0), ('B', 1), ('C', 2), ('?', 3), ('?', 4)]
print(c)

把输入的各个元素扩展成多个输出元素的生成器函数

这里写图片描述

cy = itertools.cycle('ABC')
# A
print(next(cy))

a = list(itertools.combinations('ABCD', 2))
# [('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D'), ('C', 'D')]
print(a)

用于重新排列元素的生成器函数

这里写图片描述

d = list(itertools.groupby('LLLLAAGGG'))
# [('L', <itertools._grouper object at 0x7fc084b957b8>), ('A', <itertools._grouper object at 0x7fc084b95860>), ('G', <itertools._grouper object at 0x7fc084b95898>)]
print(d)

可迭代的归约函数

函数接受一个可迭代的对象,然后返回单个结果。这些函数叫“归约”函数、 “合拢”函数或“累加”函数。
这里写图片描述

all([1, 2, 3]) # True
all([1, 0, 3]) # False

any([1, 0, 3]) # True

深入分析iter函数

iter 函数还有一个鲜为人知的用法:传入两个参数,使用常规的函数或任何可调用的对象创建迭代器。这样使用时,第一个参数必须是可调用的对象,用于不断调用(没有参数),产出各个值;第二个值是哨符,这是个标记值,当可调用的对象返回这个值时,触发迭代器抛出 StopIteration 异常,而不产出哨符。

import random
def d6():
    return random.randint(1, 6)

d6_iter = iter(d6, 1)

print(d6_iter) # <callable_iterator object at 0x7f66f98f27b8>

# for循环会一直执行,知道i的值为1时,停止。
for i in d6_iter:
    print(i)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值