python中对生成器和迭代器的理解(yield、next()、iter())

在介绍生成器和迭代器之前,先来说下__iter__()和iter()函数以及__next__()和next(),理解迭代器必须理解这两个函数。

学过面向对象,大家应该知道对象中的方法以__开头和以__结尾的方法都是内置属性,那么这里的__iter__和__next__也是类模板中定义的,一般我们不在外面代码中去直接调用这两个东西。
所以说其实__iter__和iter()是一个功能,都是返回迭代器对象,__next__和next()是返回迭代器对象的下一个元素的值。
只是带__的是在类中定义的时候使用。这个类也就是实现了迭代器的功能,既然类已经实现了迭代器的功能,那么实例化之后的对象也就是迭代器了,就无需再去调用iter()函数了。

我理解的iter()和next()是python自带的封装好的外部函数,可以提供给用户使用,前者用来生成一个迭代器,后者则是配合迭代器使用,用来输出迭代器对象的下一个元素。

用户如果想自己定义一个迭代器就可以使用__iter__()和__next__()这两个方法。

以上是关于__iter__()和iter()函数以及__next__()和next()个人的一点理解,之前一直搞不明白。

下面开始来介绍迭代器。
一、迭代器
介绍迭代器之前,先来介绍可迭代对象,迭代从字面理解,就是一个重复的动作,可迭代对象就是反复的对这个对象去执行相同的操作,这个动作一般就是不断的去读取对象中下一个值。
百度搜出来迭代的定义是这样的:迭代是一个环结构,从初始状态开始,每次迭代都遍历这个环,并更新状态,多次迭代直到到达结束状态。
那么一个对象可迭代的话,肯定需要元素满足多,单一的比如数字,就一个无法进行反复的操作,迭代对它来说没什么意义。
所以,python中字符串、元组、列表、字典、集合都有多这个特点,都是可迭代的。

可迭代的对象,需要python内部实现__iter__()这个方法。
字符串、元组、列表、字典、集合之所以是可迭代对象,原因就是python底层定义了__iter__()这个方法,所以是可迭代的对象。

那么可迭代的话,比如[1,2,3,4],第一次迭代的元素值是1,下一次继续迭代的话,下一次迭代如何知道上次迭代到哪里了,解决这个问题,所以就有了迭代器这个概念,它的作用就是记住上一次迭代的位置。

迭代器对象需要满足其类实现了__iter__()和__next__()这两个方法。

from collections.abc import Iterable,Iterator
class MyNumber(object):

    def __init__(self):
        self.a = 0
    def __iter__(self):
        return self
    def __next__(self):
        self.a += 1
        if self.a >10:
            raise StopIteration
        return self.a

m = MyNumber()
print(isinstance(m,Iterable))
print(isinstance(m,Iterator))

运行结果:

True
True

如果将上述的MyNumber这个类中的__next__()方法注释掉,则m对象就不是迭代器了,只是可迭代对象。

如果去调用iter(m)生成迭代器,会报错:

class MyNumber(object):

    def __init__(self):
        self.a = 0

    def __iter__(self):
        return self
        m = MyNumber()
print(isinstance(m,Iterable))
print(isinstance(m,Iterator))
mm = iter(m)

运行结果:

True
False
Traceback (most recent call last):
  File "C:/Users/10270/Desktop/py_test/test_10_3.py", line 44, in <module>
    mm = iter(m)
TypeError: iter() returned non-iterator of type 'MyNumber'

一开始我预期的是True,结果抛了异常,因为我觉得iter()这个是python封装好的外部函数,可以执行使用,我的m已经是可迭代对象了,为什么不能调用iter()函数生成一个迭代器,经过打断点,发现在执行iter(m)这条语句的时候会去调用MyNumber类中用户自定义的__iter__()方法,而不是去调用我以为的python的外部函数iter()。这说明什么,在调用iter()函数的时候,其实python应该也是去调用可迭代对象__iter__的这个方法,之所以元组、列表能够调用成功,是因为内部的__iter__方法是符合产生迭代器的,而我这里的可迭代对象m的类中已经定义了__iter__方法,则会去调用我自己的__iter__方法,这个方法只是返回了一个self,无法满足生成迭代器的条件,迭代器需要有__next__方法才可以。经过对上述的代码进行改进,就可以了。

class MyNumber(object):
    def __init__(self):
        self.a = 0
    def __iter__(self):
        return B()
    # def __next__(self):
    #     self.a += 1
    #     if self.a >10:
    #         raise StopIteration
    #     return self.a
class B(MyNumber,object):
    def __next__(self):
        self.a += 1
        if self.a >10:
            raise StopIteration
        return self.a

m = MyNumber()
print(isinstance(m,Iterable))
print(isinstance(m,Iterator))
mm = iter(m)
print(isinstance(mm,Iterator))

运行结果:

True
False
True

所以我的猜测是对的。
就是迭代器生成的条件是:
①实现了__iter__方法和__next__方法,由这个类生成的对象一定是迭代器。即使两个方法中什么也不做(如下例)。

class MyNumber(object):

    def __init__(self):
        self.a = 0

    def __iter__(self):
        pass
    def __next__(self):
        pass
m = MyNumber()
print(isinstance(m,Iterable))
print(isinstance(m,Iterator))

运行结果:

True
True

②如果实现了__iter__这个方法,但是返回的对象是一个实现了__next__方法的可迭代对象。

class MyNumber(object):
    def __init__(self):
        self.a = 0
    def __iter__(self):
        return B()
    # def __next__(self):
    #     self.a += 1
    #     if self.a >10:
    #         raise StopIteration
    #     return self.a
class B(MyNumber,object):
    def __next__(self):
        self.a += 1
        if self.a >10:
            raise StopIteration
        return self.a

m = MyNumber()
print(isinstance(m,Iterable))
print(isinstance(m,Iterator))
mm = iter(m)
print(isinstance(mm,Iterator))

上面的return B() 这里就是对类B实例化一个对象,该对象实现了__next__方法,且继承了MyNumber的__iter__方法。所以m满足了__iter__和__next__方法。所以是迭代器。

二、生成器
什么是生成器,比较抽象!函数中使用了yield的就是生成器。生成器也是迭代器,一个函数被定义成生成器,如果想使用yield返回的数据,需要调用next()函数。

yield和return的区别:函数被调用之后,return是直接返回数据,函数的环境会被回收,但是yield返回数据之后,函数的环境还会保留,当下一个next被调用时,会继续回到上一个yield执行到那里接着执行后面的语句,直到遇到yield会返回数据,环境继续保留…反复,只要调用next()就会继续,遇到下一个yield就会继续,遇不到则会抛StopIteration异常。

def test():
    print("test")
    yield 1
    print("祖国母亲,您生日快乐!")
    yield 2
    print("第二次")

try:
    f = test()
    print(type(f))
    next(f)
    next(f)
    next(f)
except StopIteration:
    print("迭代结束")

运行结果:

test
祖国母亲,您生日快乐!
第二次
迭代结束

从上面可以看出函数中可以有多个yield,但是return如果有多个的话,不会语法错误,但没有实际意义,第一个return后面的语句没有任何用,永远不会执行,因为return之后,函数的环境会被回收,下一次函数调用会重头开始。

综上:生成器就是只要用户加以设计代码能够源源不断的产生数据的一个东西,且下一次生成数据的环境是在上一次生成数据保留的环境基础上进行生成数据。

实现生成器有两种方式:一是用户自定义函数,函数中需要有yield修饰。二是,通过列表生成式的变种.
列表生成式:
[x for x in range(10)]
这个是一个产生0-9的一个列表[1,2,3,4,5,6,7,8,9]
将上面的改成(x for x in range(10)),并赋值给g,那么g就是一个生成器,同时也是一个迭代器,也是可迭代对象,可以用for进行遍历,可以使用next()函数进行不断的调用产生列表中数据。
注意:以下两个例子是我在学习的过程中遇到的坑,第一个例子我搞懂了,第二个我没明白,但是知道怎么改代码,不会报错。希望看到我这篇文章的小伙伴,能够解答我的疑问。
坑1:

def test():
    count = 0
    while True:
        count += 1
        print(count)
        yield 1
        if count >= 2:
            return

try:
    # f = test()
    # print(type(f))
    next(test())
    next(test())
    next(test())
except StopIteration:
    print("迭代结束")

运行结果:

1
1
1

这结果看到挺崩溃的。为啥没有按照我想的:返回如下结果

1
2
迭代结束

原因就是:next(test()),这里的test()每次都是不一样的,所以每次都是重新去调用,不是上一次test()调用的位置继续执行。
将上述try中的代码改成下面这样就可以了:

try:
    f = test()
    # print(type(f))
    next(f)
    next(f)
    next(f)
except StopIteration:
    print("迭代结束")

上述的f就调用一次,后面每次next()都是用的这个f。

坑2:

def test():
    count = 0
    while True:
        count += 1
        print(count)
        yield 1
        if count >= 2:
            raise StopIteration

try:
    f = test()
    # print(type(f))
    next(f)
    next(f)
    next(f)
except StopIteration:
    print("迭代结束")

运行结果:

1
2
Traceback (most recent call last):
  File "C:/Users/10270/Desktop/py_test/test_10_4.py", line 28, in test
    raise StopIteration
StopIteration

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:/Users/10270/Desktop/py_test/test_10_4.py", line 35, in <module>
    next(f)
RuntimeError: generator raised StopIteration

可以看到抛异常了,为啥我对异常捕获了,怎么没有捕获到???就是因为我raise StopIteration这个导致的,把它改成return 就不会这样,我也不知道为啥?可能是我异常还是没有学好,是不是因为我自己抛了一个异常,然后next没有拿到值又抛了一次?不理解?有大神懂得吗?帮忙解答以下。万分感谢!

send方法:
最近又温习了生成器、发现之前学习的还不够充分。
比如生成器中的send()方法,throw()方法。还有我发现上面我的yield写法都是:
yield 后面跟一个值:

def gen():
    yield 1

g = gen()
print(next(g))

比如上面的那样,如果写成下面这样:

def gen():
    while True:
        tmp = yield 1
        print(tmp)

g = gen()
print(next(g))
print(next(g))

上面把yield前面加上了给tmp变量赋值,按我之前的理解是会把1给tmp,那么下一个next()之后,打印的就是1,实际并不是。。。。此时我懵了。。。我原来之前理解错了。
把上面代码执行一下看看。

1
None
1

看到没。是None!!!
原因就是给tmp赋的值不是yield返回给next的值,而是通过send()方法接受的值,而我们上面的代码中并没有调用send方法,所以默认就是None。

下面我们来调用一下next方法。

def gen():
i= 0
    while True:
        tmp = yield i
        print(tmp)
        i += 1

g = gen()
print(next(g))
print(next(g))
re = g.send("我传值了,你接受我吧。")
print(re)

返回结果:

0
None
1
我传值了,你接受我吧。
2

从上面的执行结果可以分析,g.send()方法调用的时候,是从上一次yield停止的位置开始,把send()方法中的参数值传给tmp接收,然后接着往下执行打印,然后i+1,然后寻找下一个yield,把yield的值返回来给re,此时i=2了,所以打印的是2.

所以g.send()也有yield返回值,只是这里会传个值给tmp。但是前提是调用send方法前,一定要调用next()方法,否则会报错,也就是send()方法是从上一次yield停止的位置开始,没有调用next,那么就会报错。

def gen():
    i = 0
    while True:
        tmp = yield i
        print(tmp)
        i += 1

g = gen()
re = g.send("我传值了,你接受吧。")
print(re)
print(next(g))

运行结果:

C:\Python36\python.exe D:/自动化B/python_test/test_10_26.py
Traceback (most recent call last):
  File "D:/自动化B/python_test/test_10_26.py", line 22, in <module>
    re = g.send("我传值了,你接受吧。")
TypeError: can't send non-None value to a just-started generator

Process finished with exit code 1

在send方法前面加上next就好了。
总结一下send方法的使用:
①调用send方法的时候,生成器函数是从它上一次yield停止的位置开始执行,所以调用send方法前,必须至少有一次next()。
②send()方法调用有返回值,返回值就是yield的返回值,环境就会停止在这里,跟next()调用差不多,只是send会传值给yield前面的值,send(“None”)就是和next()一样了。

throw()方法
throw方法的使用和send差不多,都是在上一次yield停止的地方开始,通过调用throw方法抛了异常给生成器函数,如果生成器函数中没有捕获,那么异常就会返回给调用者,下面的例子是捕获了对象,捕获之后代码继续执行,遇到下一个yield就停止,如果遇不到就抛StopIteration异常。

def gen():
    while True:
        try:
            yield "我是try里面的"
        except ValueError:
            print('我是except...')
        finally:
            print('我是finally....')

g = gen()

print(next(g))
gg = g.throw(ValueError)
print(gg)

运行结果:

我是try里面的
我是except...
我是finally....
我是try里面的
我是finally....

下面给出一个生成器函数中没有捕获异常的例子,异常会直接返回给调用对象,且后续的代码不会执行。

def gen(i):
    while True:
        yield i
        i += 1

g = gen(1)

print(next(g))
gg = g.throw(ValueError)
print(gg)

运行结果:

1
Traceback (most recent call last):
  File "D:/自动化B/python_test/test_10_26.py", line 17, in <module>
    gg = g.throw(ValueError)
  File "D:/自动化B/python_test/test_10_26.py", line 11, in gen
    yield i
ValueError

上面还有一个注意事项,就是此时生成器对象已经退出了,后面如果再调用next()会报错StopIteration,如下代码:

def gen(i):
    while True:
        yield i
        i += 1
try:
    g = gen(1)
    print(next(g))
    gg = g.throw(ValueError)
    print(gg)
except Exception :
    print("有异常。。。")

print(next(g))

运行结果:

1
有异常。。。
Traceback (most recent call last):
  File "D:/自动化B/python_test/test_10_26.py", line 23, in <module>
    print(next(g))
StopIteration

还有一种抛异常不是通过throw方法了,是生成器自己会主动抛异常,如果生成器函数退出的时候,没有遇到yield关键字,就会抛异常:StopIteration。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

如梦@_@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值