在介绍生成器和迭代器之前,先来说下__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, inmm = 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__方法。所以mm满足了__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 >= 3:
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
next(f)
RuntimeError: generator raised StopIteration
可以看到抛异常了,为啥我对异常捕获了,怎么没有捕获到???就是因为我raise StopIteration这个导致的,把它改成return 就不会这样,我也不知道为啥?可能是我异常还是没有学好,是不是因为我自己抛了一个异常,然后next没有拿到值又抛了一次?不理解?有大神懂得吗?帮忙解答以下。万分感谢!



