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)
        
>>> s = Sentence('"The time has come," the Walrus said,') 
>>> s
Sentence('"The time ha... Walrus said,') 
# s可以迭代,因为实现了__getitem__方法
>>> for word in s: 
... print(word)
The
time
has
come
the
Walrus
said
# 因为可以迭代,所以Sentence 对象可以用于构建列表和其他可迭代的类型
>>> list(s) 
['The', 'time', 'has', 'come', 'the', 'Walrus', 'said']

序列可迭代的原因:iter函数,解释器需要迭代对象x时,会自动调用iter(x), 内置的iter有一下作用:

  • 检查对象是否实现了__iter__方法,如果实现了就调用它,获取一个迭代器。
  • 如果没有实现__iter__ 方法,但是实现了__getitem__ 方法,Python 会创建一个迭代器,尝试按顺序(从索引0 开始)获取元素。
  • 如果尝试失败,Python 抛出TypeError 异常,通常会提示“C object is not iterable”(C对象不可迭代),其中C 是目标对象所属的类

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

使用iter内置函数可以获取可迭代对象的迭代器

  • 如果对象实现了能返回迭代器的__iter__方法,那对象就是可迭代的
  • 序列都可以迭代;实现了__getitem__方法,而且其参数是从0开始索引,这种对象也可以迭代
  • 我们要明确可迭代对象和迭代器之间的关系,python从可迭代对象中获取迭代器
>>> S = "ABC"
# 使用可迭代对象构建迭代器it
>>> it = iter(s)
>>> it
<str_iterator at 0x7f08cc460e10>

>>> while True:
... try:
...     print(next(it)) 
... except StopIteration: 
...     del it 
...     break
A
B
C

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

  • __next__:返回下一个可用的元素,如果没有元素了,抛出StopIteration 异常
  • __iter__:返回self,以便在应该使用可迭代对象的地方使用迭代器
import collections
# 检查对象是否为迭代器
>>> isinstance(iter('ABC'), collections.abc.Iterator)
True

>>> s3 = Sentence('Pig and Pepper') 
>>> it = iter(s3) 
>>> it 
<iterator object at 0x...>
>>> next(it) 
'Pig'
>>> next(it)
'and'
>>> next(it)
'Pepper'
# 没有单词了,抛出StopIteration异常
>>> next(it) 
Traceback (most recent call last):
...
StopIteration
# 到头后,迭代器没用了
>>> list(it) 
[]
# 如果想再次迭代,要重构迭代器
>>> list(iter(s3)) 
['Pig', 'and', 'Pepper']

典型的迭代器

我们自己写一个类来实现迭代器

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)
    
    # 为了表明这个类可以迭代,实现了__iter__方法
    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

注意,对这个示例来说,其实没必要在SentenceIterator 类中实现__iter__ 方法,不过这么做是对的,因为迭代器应该实现__next__ 和__iter__ 两个方法,而且这么做能让迭代器通过issubclass(SentenceInterator, abc.Iterator) 测试。如果让SentenceIterator 类继承abc.Iterator 类,那么它会继承abc.Iterator.__iter__ 这个具体方法.

构建可迭代的对象和迭代器经常会出现错误,原因是混淆了两者:

  • 可迭代的对象有个__iter__ 方法,每次都实例化一个新的迭代器
  • 而迭代器要实现__next__ 方法,返回单个元素,此外还要实现__iter__ 方法,返回迭代器本身。
  • 迭代器可以迭代,但是可迭代的对象不是迭代器
  • 可迭代的对象一定不能是自身的迭代器。也就是说,可迭代的对象必须实现
    __iter__ 方法,但不能实现__next__ 方法

生成器函数

我们可以用生成器函数代替SentenceIterator类,实现相同的功能

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

在上例中的__iter__函数中,

  • 这个return语句不是必要的;这个函数可以直接“落空”,自动返回。不管有没有return 语句,生成器函数都不会抛出StopIteration 异常,而是在生成完全部值之后会直接退出。
  • __iter__方法是生成器函数,调用是会构建一个实现了迭代器接口的生成器对象
生成器函数的工作原理

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

>>> def gen_123(): # 只要函数包含yield关键字,就是生成器函数
...     yield 1 
...     yield 2
...     yield 3
...
>>> gen_123 # 函数对象
<function gen_123 at 0x...> 
>>> gen_123()  # 调用函数,返回生成器对象
<generator object gen_123 at 0x...> 
>>> for i in gen_123():  # 生成器也是迭代器
...     print(i)
1
2
3
>>> g = gen_123() 
>>> next(g) # 因为g是迭代器,可以调用next(g)获取下个元素
1
>>> next(g)
2
>>> next(g)
3
>>> next(g) 
Traceback (most recent call last):
...
StopIteration

生成器也是迭代器,可以调用next方法获取下一个元素

生成器函数定义体执行过程
>>> def gen_AB(): 
...     print('start')
...     yield 'A' 
...     print('continue')
...     yield 'B' 
...     print('end.') 
...
>>> for c in gen_AB(): 
...     print('-->', c) 
...
start 
--> A 
continue 
--> B 
end.
>>>

Sentence类的惰性实现

设计Iterator接口时考虑了惰性:next(my_iterator)一次生成一个元素。之前实现的Sentence类中都不具有惰性,因为__init__方法构建好了单词列表,绑定到self.words属性上,很耗内存,我们将类改进如下:

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

class Sentence:
    def __init__(self, text):
        # 不再需要words列表
        self.text = text 
        
    def __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)
        
    def __iter__(self):
        ''' finditer 函数构建一个迭代器,包含self.text 
        中匹配RE_WORD 的单词,产出MatchObject实例'''
        for match in RE_WORD.finditer(self.text): 
            yield match.group()

生成器表达式

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

>>> def gen_AB(): #
...     print('start')
...     yield 'A'
...     print('continue')
...     yield 'B'
...     print('end.')
...
# 列表推导急切的迭代gen_AB()函数
>>> 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讯号迭代res2时,gen_AB()函数的定义体才会真正执行
for 循环每次迭代时会隐式调用next(res2),前进到gen_AB 函数
中的下一个yield 语句。注意,gen_AB 函数的输出与for 循环中
print 函数的输出夹杂在一起。
"""
>>> for i in res2: 
... print('-->', i)
...
start
--> AAA
continue
--> BBB
end.

关于何时使用生成器表达式:
如果生成器表达式要分成多行写,倾向于定义生成器函数,以便提高可读性。此外,生成器函数有名称,因此可以重用

等差数列生成器

我们定义一个等差数列生成器ArithmeticProgression类来说明如何使用生成器函数实现__iter__方法

class ArithmeticProgression:
    def __init__(self, begin, step, end=None): 
        self.begin = begin
        self.step = step
        self.end = end # None # 无穷数列
        
    def __iter__(self):
        result = type(self.begin + self.step)(self.begin) 
        forever = self.end is None 
        index = 0
        while forever or result < self.end: 
            yield result 
            index += 1
            result = self.begin + self.step * index
            
>>> ap = ArithmeticProgression(0, 1, 3)
>>> list(ap)
[0, 1, 2]
>>> ap = ArithmeticProgression(1, .5, 3)
>>> list(ap)
[1.0, 1.5, 2.0, 2.5]
>>> ap = ArithmeticProgression(0, 1/3, 1)
>>> list(ap)
[0.0, 0.3333333333333333, 0.6666666666666666]

# 使用itertools生成等差数列
>>> import itertools
>>> gen = itertools.count(1, .5)
>>> next(gen)
1
>>> next(gen)
1.5
>>> next(gen)
2.0
>>> next(gen)
2.5

可迭代归约函数

可迭代归约函数接受一个可迭代对象,然后返回单个结果。典型的归约函数如下:

  • all(it): it中所有元素为真是返回True,否则返回False,all([])返回True
  • any(it): 只要it中有元素为真值就返回True,否则返回False, any([])返回False
  • min(it)
  • reducr(func, it)
  • sum(it)
# g is a generator
>>> g = (n for n in [0,5,2,1])
>>>any(g)
True

对于any和all函数来说,有一个重要的优化措施是reduce做不到了,这两个函数会短路(即一旦确定结果就立即停止使用迭代器)。

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

下述示例展示如何使用iter 函数掷骰子,直到掷出1 点为止

>>> def d6():
...     return randint(1, 6)
...
>>> d6_iter = iter(d6, 1)
>>> d6_iter
<callable_iterator object at 0x00000000029BE6A0>
>>> for roll in d6_iter:
...     print(roll)
...
4
3
6
3

一个常见的用法是用于文件读取,这段代码逐行读取文件,直到遇到空行或者到达文件末尾为止

with open('mydata.txt') as fp:
    for line in iter(fp.readline, '\n'):
        process_line(line)
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值