一、迭代器
1、通俗理解
可以想象一下,你在计算机中做了一个加法,例:1+1=2 ,但是如果你在按 ‘= ’(等于符号),计算器会自动往下递增1,每按一次等于号,会+1个数字,那么就可以大概理解为,一个容器中存储了很多个东西,但是这个东西,按一定的规律规则来存储计算生成,但是容器中不可能什么东西都有吧,对的,人为的就会设一个限制,当超出这个限制范围,就会报异常
当然如果学过C语言的话,可以理解为这个空间存储了一个复杂的指针指向了很多地址,他实质不会占用很多内存,因为存储的是地址(这块可能理解的不到位,若有错,请指明)
迭代器最大的优点就是节省了空间,因为如果你想存储0~9这个字符串,你就需要劈开10个空间,但要是有了迭代器,那么你就开辟了一个空间
那么又会想到有了for 循环为什么不用for循环,而专门弄了一个抽象的迭代器呢,是因为,for循环就像是一个实例化的迭代器,已经具备了定义好的某种功能,而有了迭代器这个抽象的东西,你就可以按照自己的需求来定义合适的迭代器
2、实例
在Python中的列表、元组、字符串、文件、映射、集合等容器中都可以在for循环中使用,而迭代器是实现了迭代器协议方法的对象或类。在每次循环中,for语句都是从迭代器序列中取一个数据元素。迭代器协议方法主要有__iter__()和—__next__()方法,前者返回对象本身,后者返回容器中的下一个元素,当已经是最后一个元素时,就会引发StopIteration的异常。(int和bool类型不能用 )
看下面实例(迭代器的内部实现)就好理解了。
class iterInner(object):
....def __init__(self,end):
........self.__start = 0
........self.end = end
....def __iter__(self):
........return self
....def __next__(self):
........if self.__start >= self.end:
............raise StopIteration
........else:
............self.__start = self.__start + 1
............return self.__start - 1
ite = iterInner(8)
for i in ite:
....print(i)
'''
0
1
2
3
4
5
6
7'''
二、生成器
1、通俗理解
通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的,而且创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。如果想要100个包子,但是吃到50个时候吃饱了,但是已经做好了100个包子,那剩下的50个包子也吃不了,不就是浪费了吗?所以就产生了生成器,吃一个包子,产一个包子
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间,在Python中,这种一边循环一边计算的机制,称为生成器:generator
生成器是一个特殊的程序,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。
2、简单说一下yield,send方法:
(1)yield方法:类似于return语句,调用到yield就会暂停,而且是一个yeield对应一个next,数量上要对应,但是yield不等于return语句,还是有一定的区别。
① return一般在函数中只设置一个,他的作用是终止函数,并且给函数的执行者返回值。
② yield在生成器函数中可设置多个,他并不会终止函数,next会获取对应yield生成的元素。
(2)send方法:直接引用其代码进行分析(最好自己调试几次便可领悟)
(第二个案例,小编又多调用了一次next,可以清楚对比出send和filed的区别)
# 1、 next只能获取yield生成的值,但是不能传递值。
def gen(name):
print(f'{name} ready to eat')
while 1:
food = yield
print(f'{name} start to eat {food}')
dog = gen('alex') # 由dog对象接受
next(dog) # 第一次传入,走gen函数,一直往下执行到yield停止,此时在等号右边
next(dog) # 第二次传入,直接接上一个走yield后面的内容,因为是一个无线循环,继续跳转到food=yield,但是由于上一执行的yield没有返回任何,所以返回的是一个None,依然遇到yield停止。
next(dog) #第三次传入,和第二个过程一样
# 执行结果如下:
# alex ready to eat
# alex start to eat None
# alex start to eat None
# 2、 而这个案例可以对比出来filed和send区别
def gen(name):
print(f'{name} ready to eat')
while 1:
food = yield 222
print(f'{name} start to eat {food}')
dog = gen('alex')
next(dog) # 第一次必须用next让指针停留在第一个yield后面(因为是赋值运算先进行等号右边)
# 与next一样,可以获取到yield的值
next(dog) #第二次,传入时可得food为None,(因为上次运行完),第二次运行完依然为None
ret = dog.send('骨头') #第三次,传入时可得food为骨头,是因为send给上一个yield传递的值,但最后运行完,food值为222(因为从等号右边运行,直到完成,最后才赋值给左边)
print(ret) #打印结果为222
# 执行结果如下:
# alex ready to eat
# alex start to eat None
# alex start to eat 骨头
# 222
def gen(name):
print(f'{name} ready to eat')
while 1:
food = yield
print(f'{name} start to eat {food}')
dog = gen('alex')
next(dog) # 执行到这步游标在yield之后的位置,返回的是一个None值
dog.send('骨头') # 第一次,此时的光标从yield后面开始执行,进入时food变传递为骨头
dog.send('狗粮') # 第二次,进入时food变传递为狗粮
dog.send('香肠') # 第三次,进入时food变传递为香肠
由Debug调试运行了下过程,可以得到send方法实质就是给上一yield返回出的值,覆盖掉并传递现有的。
send注意!!!:yield和send同时在,不可在第一次就直接send,因为程序不知道你再搞什么,应该先执行一次next,让yield先走一遍,在去调用send,这样就可以把send传入的值返回给上一个yield,如果非要想这么做,可以send(None) 让第一次传入的是个None,(提示必须有个参,但又不能传入其他参数)
3、总结
生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器