Python3 基础 -- 迭代器与生成器 !

约定:

「yield值」:指「生成器函数」返回的值,即,yield关键字后面的表达式的值

可迭代对象(iterable)

「可迭代对象」:简单的概括就是,能从其获取一个「迭代器对象」的对象。

所有的序列类型(例如:list,str和tuple)和一些非序列类型(例如:dict,file)对象,以及任何用户定义的包含__iter__()函数或者__getitem__()函数的类的对象(一般为用作容器的对象)都是「可迭代对象」。

__iter__()函数:返回一个「迭代器对象」。

**Python学习交流群:1004391443,这里是python学习者聚集地,有大牛答疑,有资源共享!小编也准备了一份python学习资料,有想学习python编程的,或是转行,或是大学生,还有工作中想提升自己能力的,正在学习的小伙伴欢迎加入学习。**

如果没有定义__iter__()函数,则看有没有定义__getitem__()函数,如果有定义,则Python会创建(一般就是iter()函数创建)一个内置的默认「迭代器对象」,该「迭代器对象」会调用__getitem__()函数。

当一个「可迭代对象」作为参数传给内置的iter()函数时,iter()函数将返回一个「迭代器对象」(它会调用刚刚谈到的__iter__()以获得一个「迭代器对象」,或是自己创建一个「迭代器对象」调用__getitem__())。通常我们没有必要主动调用iter()函数,for循环语句自动调用iter()函数,在循环执行期间会暂存这个返回的「迭代器对象」。

迭代器对象(iterator)

首先了解一个协议,「迭代器协议」:

「迭代器协议」定义了两个函数:

__iter__():返回「迭代器对象」自己。(实现时,如果你很确定该对象肯定不会作为「可迭代对象」使用,也可以不实现这个函数,但是很显然,你不确定,所以还是实现这个函数吧。)

__next__():从容器中返回下一项,如果没有下一项了,则抛出StopIteration异常。

「迭代器类型」:遵循「迭代器协议」的类型

「迭代器对象」:「迭代器类型」的实例对象。

反复的调用「迭代器对象」的__next__()函数(将「迭代器对象」作为参数传递给内置的next()函数会调用「迭代器对象」的__next__()函数)将连续返回容器中的数据。当没有更多的数据可用时,将会抛出StopIteration异常,此时「迭代器对象」已经耗尽了它的数据,以后再次调用它的__next__()都只会抛出StopIteration异常(可以概括为:对于这个「迭代器对象」,一次抛出StopIteration,终身抛出StopIteration)。

「迭代器对象」的__iter__()函数返回「迭代器对象」本身,这样每个「迭代器对象」也是一个「可迭代对象」,可使用「可迭代对象」的任何地方都可以使用「迭代器对象」。

有一点需要注意:一个「可迭代对象」每次作为参数调用iter()函数或者用于for循环时,都将生成一个新的迭代器对象。

下面我们来实现一个自己的「可迭代对象」和「迭代器对象」:

#这是一个迭代器类型
class iterObj:
    def __iter__(self):#实现这个函数,便可执行iter(iterObj对象)
        return self
    def __next__(self):#实现这个函数,便是迭代器类型
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]
#这是一个可迭代类型
class Reverse:
    def __init__(self, data):
        self.data = data
        self.index = len(data)
    def __iter__(self):#实现了这个函数,便是可迭代的
        iter = iterObj()
        iter.data = self.data
        iter.index = len(self.data)
        return iter
    # def __getitem__(self, index):#如果没有实现__iter__函数,但是实现了这个函数,则python创建的迭代器对象会调用该函数。
        #如果想提前结束,可以抛出异常
        # if index == 1:
        #     raise StopIteration
        # return self.data[index]
rev = Reverse('spam')
for char in rev:
    print(char)

关于iter()函数

通常使用iter()函数都只是传递一个参数,但其实它接受两个参数:

iter(object[,sentinel]):如果只传了第一个参数,那么这个对象必须实现了__iter__()函数或者实现了__getitem__()函数,否则会抛出TypeError的异常。在我们刚刚编写的例子就符合要求。

如果传递了第二个参数,那么第一个参数必须是「可调用的对象」(例如函数),iter()函数会创建一个「迭代器对象」,这个「迭代器对象」的__next__()函数会调用第一个参数,当第一个参数返回的值等于第二个参数时,抛出StopIteration异常,否则返回该值。来看一个例子:

class Reverse:
    def __init__(self, data):
        self.data = data
        self.index = len(data)
    def callable(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]
rev = Reverse('spam')
ite = iter(rev.callable, 'a')
print(ite)#<callable_iterator object at 0x1101fe5c0>
for char in ite:#打印m
    print(char)

生成器对象(Generators)

「生成器对象」其实也是「迭代器对象」,因为从「生成器对象」中获取「迭代器对象」时只是简单的把自己返回了(类似我们前面自己编写的例子),但是它特殊就特殊在yield语句上。

当一个函数中含有yield语句时,那么称这个函数为「生成器函数」。

当我们定义了一个「生成器函数」时,__iter__()和__next__()会被自动定义(不论是通过一个内置的「生成器类」还是其他办法,总之,这两个函数被自动定义了),当调用这个「生成器函数」时,会返回一个「生成器对象」。

上一节我们自己编写了一个「迭代器」例子,相比较后我们可以猜到,其实Python为「生成器」自动创建的那两个函数与我们自己写的在目的上没什么区别,无非都是为了实现「迭代器协议」,从这个角度来说,前面我们自己写的Reverse类的实例,也可以称为「生成器对象」,只是在具体实现上有区别,特别是__next__()函数,至于「生成器对象」的其他特性,我们照样可以自己加上。

「生成器对象」的__next__()函数会启动「生成器函数」的执行或者从上一次执行yield语句的地方恢复执行,直到遇到下一个yield语句后,挂起(或者称为暂停),同时将yield关键字后面的表达式的值返回给调用者(对于挂起,我们只需要知道Python会把当时的执行状态保存下来以便下次恢复)。来看一个例子:

def reverse(data):
    print('reverse is called')#3
    for index in range(len(data)-1, -1, -1):#4 #10
        yield data[index]#5 #8 #11
        print('执行yield表达式以后的语句')#9
#这里并没有输出'reverse is called',仅仅是创建了一个生成器对象,并没有执行reverse函数。
re = reverse('golf')#1
#for语句会调用iter(re),接着会调用返回对象的__next__()函数
for char in re:#2 #7
    print(char)#6 
#输出:f l o g

执行过程是这样的:

#1 创建一个「生成器对象」。

#2 for语句会调用iter(re),返回对象其实就是re本身(其实这里也相当于创建了一个迭代器),接着会调用返回对象的__next__()函数,__next__()函数启动执行「生成器函数」。

#3 打印

#4 执行for循环的第一次循环

#5 遇到「yield表达式」挂起,返回「yield值」(这里就是:data[index]),执行流回到调用者

#6 此时char已经被赋值,就是#5中「yield表达式」的值,打印

#7 继续执行for循环,再次调用__next__()

#8 从步骤5挂起的地方恢复执行

#9 执行打印:执行「yield表达式」以后的语句

#10 继续执行for循环

#11 再次遇到「yield表达式」回到步骤#5

直到这个由「生成器对象」创建的「迭代器对象」耗尽,执行结束。

yield表达式

到这里是不是觉得对yield有点感觉了?请先在新里面默念三遍:「yield 100」是一个表达式。

我们接着来看,下面例子中m的值分别为多少?

def yield_func():
    print('— first next() is called---')
    m = yield 100 #1
    print('— second next() is called—')
    print(m)
    m = yield 100 #2
    print('— send(value) is called—')
    print(m)
yld = yield_func()
next(yld)#输出— first next() is called—
next(yld)#输出—-second next() is called—- 和 None
yld.send(1)#输出—-send(value) is called—-和 1 并且抛出异常:StopIteration

上面m的值依次打印为None和1:

当第一个next()执行的时候,仅仅打印一行文字;

然后执行第二个next()函数,此时从#1处恢复,恢复时yield 100表达式的返回值赋给了m,那这个表达式的值是多少?从后面的打印可以看到是None;

然后是执行yld.send(1),此时从#2处恢复,恢复时仍然是把yield 100表达式的值赋给了m,这次打印出来m的值为:1,恰好是我们传进去的值。

结论:

「生成器对象」的send(value)函数会恢复执行「生成器函数」,同时把参数作为当前恢复的「yield表达式」的返回值,而next()(其内部调用了「生成器对象」的__next__()函数)相等于send(None),即,当通过next()恢复时,当前恢复的「yield表达式」的值为None。

至于最后的那个异常,这是常规操作,当一个迭代器迭代完成后,就会抛出这个异常,上一节我们自己实现的那个迭代器,也是抛出这个异常,之前这个异常为什么没有中断程序是因为内置的Python实现帮我们捕获了这个异常。

需要注意一点:由于最开始时并没有「yield表达式」接收这个传进来的值,所以当启动「生成器函数」时,需要传空值,即send(None)或者使用next()

「生成器对象」(除了__next__()和send(value))还有其他两个函数:

generator.throw(type[,value[,traceback]]):从「生成器函数」挂起的地方抛出一个type类型的异常,并返回下一个「yield值」,如果「生成器函数」没有返回「yield值」就退出了,那么会抛出StopIteration异常。

generator.close():从「生成器函数」挂起的地方抛出一个GeneratorExit异常。如果此 后「生成器函数」正常执行结束则关闭(此时抛出的GeneratorExit异常被捕获了),如果继续返回「yield值」则会抛出RuntimeError。

最后来看一个综合的例子:

def echo(value=None):
    print("Execution starts when 'next()' is called for the first time.")
    try:
        while True:
            try:
                value = (yield value)
            except Exception as e:
                value = e
                #break #这里如果直接beak退出循环,则「生成器函数」执行结束退出,调用generator.throw(TypeError, "spam”)时,会抛出StopIteration异常
            #except: #这里如果捕获所有的异常,则while循环会继续执行,此时会close()抛出RuntimeError
            #    pass
    finally:
        print("Don't forget to clean up when 'close()' is called.")
generator = echo(1)
print(next(generator))#输出:Execution starts when 'next()' is called for the first time.和 1
print(next(generator))#输出:None
print(generator.send(2))#输出:2
generator.throw(TypeError, "spam")#输出:TypeError('spam',)
generator.close()#输出:Don't forget to clean up when 'close()' is called.

这里提醒一下:例子中如果抛出的异常被捕获,仅仅执行赋值,然后while循环会继续执行。

猜你喜欢

转载自blog.csdn.net/weixin_39108368/article/details/89885685