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

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

迭代器用于从集合中取出元素,而生成器用于“凭空”产生元素。

  • 可迭代对象
  • 迭代器
  • 生成器
  • iter函数

1 什么是可迭代对象?

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

如果对象实现了能返回迭代器的__iter__方法,那么对象就是可迭代的;序列都可以迭代;实现了__getitem__方法,而且其参数是从0开始的索引,这种对象也是可迭代的。Python从可迭代的对象中获取迭代器,且可迭代的对象一定不能是自身的迭代器,即可迭代的对象必须实现__iter__,但不能实现__next__

示例1,实现__getitem__方法的可迭代对象Word类:

class Word:
    """实现了__getitem__但没有实现__iter__"""
	def __init__(self, text):
        self.words = text.split(" ")
        
	def __getitem__(self, index):
	    return self.words[index]
    
    def __len__(self):
	    return len(self.words)

Word对象实例演示如下:

>>> w = Word("I am a student")
>>> for x in w:#
...     print(x)
...
I
am
a
student

示例1演示表明,Word实例可以迭代:实现了__getitem__方法且下标从0开始的类对象(Word类对象w)可以迭代。当然,你可能发现Word类实现了序列协议(即__getitem____len__方法),而序列都可以迭代,不过去掉__len__方法时Word类实例依然可以迭代。

2 迭代器

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

  • __next__:返回下一个可用对象,如果没有元素了,抛出StopIteration异常。
  • __iter__:返回self,以便在应该使用可跌代对象的时候使用迭代器。

迭代器实现了无参的__next__方法,返回序列的下一个元素;如果没有元素了,抛出StopIteration异常。迭代器还实现了__iter__(迭代器的__iter__应该返回自身,即self)方法,因此迭代器可以迭代。

示例2,实现一个迭代器类Reverse:迭代该对象实例将得到反转信息。

class Reverse:
    def __init__(self, data):
        self.data = data
        self.index = len(data)
        
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]
>>> eng = ['one','two','three','four','fife']
>>> for i in Reverse(eng):
...     print(i)
...
fife
four
three
two
one
>>>

3 生成器

所有生成器都是迭代器,因为生成器完全实现了迭代器的接口。

只要函数定义体中有yield关键字,该函数就是生成器函数;调用生成器函数,会返回一个生成器对象:生成器会创建一个生成器对象,包装生成器函数的定义体;把生成器(gen)传给next(gen)函数时,生成器函数会向前,执行函数定义体中下一个yield语句,返回产出的值,并在函数定义体的当前位置暂停;最终,函数的定义体返回时,外层的生成器对象会抛出StopIteration异常。

3.1 生成器函数

示例3,定义一个生成器函数gen_func,该函数定义体只有3个yield语句。

def gen_fun():
    yield 1
    yield 2
    yield 3

调用生成器函数返回一个生成器对象,生成器是迭代器,会生产yield语句的值。

>>> g = gen_func()
>>> g                                                     
<generator object gen_func at 0x0000016C0BAF5D58>         
>>> for x in g:#
...     print(x)
...
1
2
3

调用next()返回下一个元素,即yield生成的下一个元素;生成器函数执行完毕时,生成器对象会抛出StopIteration异常。

>>> g = gen_func()
>>> next(g)#
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)#
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> 

3.2 yield from语句

如果生成器函数需要产出另一个生成器的值,传统解决方法是使用嵌套的for循环。如下:

def chain1(*iterables):
    for it in iterables:
        for i in it:
            yield i

上述代码与下述代码完全等效,yield from it代替了内层的for循环。

def chain2(*iterables):
	for it in iterables:
		yield from it

运行结果如下所示:

>>> s = 'ABC'
>>> t = (1,2,3)
>>> list(chain1(s,t))
['A', 'B', 'C', 1, 2, 3]
>>> list(chain2(s,t))
['A', 'B', 'C', 1, 2, 3]

4 iter()内置函数

4.1 iter函数作用:

  1. 检查对象是否实现了__iter__方法,如果实现了就调用它,获取一个迭代器(将可迭代对象转换为迭代器);

  2. 如果没有实现__iter__方法,但实现了__getitem__方法,Python会创建一个迭代器,尝试按顺序(从索引0开始)获取元素;

  3. 如果尝试失败,Python抛出TypeError异常,通常会显示C object is not iterable(C对象不可迭代)。

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

如下示例4定义了两个类:Word类实现了__getitem__方法,Foo类实现了__iter__方法。

class Word:
    """实现了__getitem__但没有实现__iter__"""
	def __init__(self, text):
        self.words = text.split(" ")
	def __getitem__(self, index):
	    return self.words[index]
    def __len__(self):
	    return len(self.words)

class Foo:
    """实现了__iter__"""
    def __iter__(self):
        pass

将可迭代对象传入iter函数,会得到一个迭代器;调用next函数会返回迭代器下一个元素,如果没有元素,next函数会抛出StopIteration异常。如下所示:

>>> w = Word("I am a student")
>>> it = iter(w)#iter(w)返回迭代器
>>> it
<iterator object at 0x0000016C0BAFD710>
>>> next(it)#调用next(it)返回迭代器下一个元素
'I'
>>> next(it)
'am'
>>> next(it)
'a'
>>> next(it)
'student'
>>> next(it)#如果没有元素,则抛出StopIteration异常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

继续往下看,下面代码信息可看出:iter()考虑遗留的__getitem__方法,调用iter(w)返回一个迭代器;而abc.Iterable不考虑遗留的__getitem__方法,它只能通过判断子类是否有__iter__方法,有则是子类(如Foo类),否则就不是其子类(如Word类)。

>>> from collections import abc
>>> w = Word("I am a student")
>>> iter(w)
<iterator object at 0x0000016C0BB28AC8>
>>> isinstance(w, abc.Iterable)
False
>>> isinstance(Foo, abc.Iterable)
True

4.2 深入分析iter()函数

iter函数有一个鲜为人知的用法:传入两个参数,使用常规的函数或任何可调用对象创建迭代器。这样使用时:

  • 第一个参数必须是可调用对象,用于不断调用(没有参数),产出各个值;
  • 第二个参数是哨值,这是个标记值,当可调用对象返回这个值时,触发迭代器抛出StopIteration异常。

示例5代码,rdint6函数随机返回1-6中的一个数字。将iter函数第一个参数设为rdint6,第二个参数(哨值)设为1

>>> from random import randint
>>> def rdint6():
...     return randint(1,6)
...
>>> it = iter(rdint6,1)
>>> next(it)
2
>>> next(it)
2
>>> next(it)
6
>>> next(it)
5
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

示例6,iter的一个例子——逐行读取文件,直到遇到空行。

with open('data.txt') as f:
    for line in iter(f.readline, '\n'):
        print(line)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值