迭代器
迭代器介绍
迭代器指的是迭代取值的工具,迭代是指一个重复的过程,每次重复执行一行代码就是迭代的过程,每一次重复都是基于上一次结果而来,迭代提供了一种通用的 不依赖索引 的迭代取值方式。
之前学习列表的时候,可以通过下标、循环遍历来进行取值,for循环是最典型的迭代器。
for i in range(10),是利用for循环把0-9中的数逐个取出来,操作是不可逆的,一直往下执行,这个过程就是迭代的过程。
可迭代对象
可以用 for 循环遍历的对象都是可迭代对象。
1.str,list,tuple,dict,set 等都是可迭代对象。dict可以先获取到items,然后通过循环取出对应的key和value值。int,float是不可迭代对象。
2. generator (生成器 和 yield 的生成器函数) 也是可迭代对象。
判断是否可迭代
1.查看是否有内置的__iter__()方法来验证是否为可迭代对象。如在pycham中输入str(),CTRL+鼠标左键点击str进去说明文档,可以看到里面有def__iter__()的方法。
2.通过 isinstance(obj,Iterable)来判断是否是可迭代对象。
from collections import Iterable
print(isinstance('abc', Iterable))
print(isinstance({1, 5, 7}, Iterable))
print(isinstance(12, Iterable))
print(isinstance(12.6, Iterable))
print(isinstance([12.6,'2a',[1,2]], Iterable))
运行的结果是:
True
True
False
False
True
迭代器
1.有内置的__iter__()方法的对象,执行迭代器的__iter__()方法得到的依然是迭代器本身
2.有内置的__next__()方法的对象,执行该方法可以不依赖索引取值。
可迭代对象不一定是迭代器,可以通过两种方法判断。
1.看是否含有__iter__()和__next__()方法。比如列表是可迭代对象,但不是迭代器,它没有next方法。
2.看是否属于Iterator
from collections import Iterator
print(isinstance([12.6,'2a',[1,2]], Iterator)) # False
iter()
可以被 next() 函数调用并不断返回下一个值的对象称为迭代器:Iterator。那我们可以通过 iter() 方法将可迭代的对象,转为迭代器。
li = [1,2,3,5]
print(type(li)) # <class 'list'>
# lis = li.__iter__()
lis = iter(li)
print(type(lis)) # <class 'list_iterator'>
迭代器对象如何取值?
可以通过 next() 或者 next()来取值,但是通常使用next()取值。
li = [1, 2, 3, 5]
lis = iter(li)
# for i in lis:
# print(i) # 可以用for循环,依次取出值
# print(lis[0]) # 不能通过下标取值
# print(lis.__next__()) # 取出 1
# print(lis.__next__()) # 取出 2
# print(lis.__next__()) # 取出 3
# print(lis.__next__()) # 取出 4
# print(lis.__next__()) # 超出长度,报错:StopIteration
print(next(lis))
print(next(lis))
print(next(lis))
print(next(lis))
print(next(lis))
注意:
- 迭代器不可以通过下标取值,而是使用 next() 或者 next() 。但是只要超出范围则直接报错 StopIteration 。
- next() 只能顺延调用,不能往前。
迭代器不一次性的取出所有数据,可以逐个取出元素,用多少取多少,减少内存的消耗。循环(for、while)的本质就是迭代器。
可迭代对象与迭代器区别
- 可用于 for 循环的都是可迭代对象
- 作用于 next() 都是迭代器对象
- list、dict、str 等都是可迭代的但不是迭代器,因为 next() 函数无法调用它们。可以通过 iter() 函数将它们转为迭代器
- python 的 for 循环本质就是通过不断调用 next() 函数实现
生成器
生成器定义
在Python 中,一边循环一边计算的机制,称为生成器:generator
上图列表中存放无限多的元素,内存占用也是很大的。如果需求是只访问前面5个元素,那后面元素所占用的内存就会被浪费掉,如果列表过大,就会浪费内存。
那么生成器就是在循环的过程中根据算法不断推算出后续的元素,这样就不用创建整个完整的列表,从而节省大量的空间。
从元素0开始,用哪个元素就取出哪个,依次用多少元素就取出多少,不用的话就停在那,等需要的时候,再进行依次取出。
相当于一个人要吃100个馒头,而厨师一下子蒸100个一起给他,既浪费时间,也占用资源,吃不完的话就会造成浪费;如果说厨师上一个,他就吃一个的话,既节省时间,也可以在吃饱的时候随时喊停。
如何创建生成器
生成器表达式
生成器表达式来源于 迭代 和 列表解析 (列表推导)的组合,生成器和列表解析(列表表达式)非常类似,但是它使用 () 而不是 []
g = (i for i in range(5))
# print(g) # generator 生成器
# print(g[0]) # 生成器不可通过下标取值
print(next(g)) # 依次取出元素值,顺延的状态,不可跳跃式取值
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g)) # 生成器也是可迭代的对象,超出取值范围会报错 StopIteration停止迭代
# 也可以用for循环控制打印的内容
for i in g:
if i <3:
print(i)
生成器函数( yield )
实现:
生成一个自定义长度的列表
需求:
- 定义函数 yieldtest
- 通过函数参数指定列表长
def yield_test(number):
n = 0
li = []
while n < number:
li.append(n)
n += 1
print(li)
yield_test(10)
当传入的实参很大的时候,比如200000,会生成很多的数,浪费内存空间。
当一个函数中包含 yield 关键字,那么这个函数就不再是一个普通的函数,而是一个 generator 。
调用函数就是创建了一个生成器对象。其工作原理就是通过重复调用 next() 或者 next() 方法,直到捕获一个异常。
# 在函数中加入yield,该函数就变为生成器函数
def yield_test(number):
n = 0
# li = []
while n < number:
yield n
# li.append(n)
n += 1
# print(li)
res = yield_test(10)
# print(res) # 生成器对象,<generator object yield_test at 0x000001E4E91B1CA8>
print(next(res)) # 0
print(next(res)) # 1
print(next(res)) # 2
print(next(res)) # 3
注意:
yield 返回一个值,并且记住这个返回值的位置,下次遇到 next() 调用时,代码从yield 的下一条语句开始执行。与 return 的差别是,return 也是返回一个值,但是直接结束函数。
# 斐波那契数列:除了第一个和第二个数以外,任何一个数都是前两个数的和
def createNums():
print('----func start----')
a, b = 0, 1
for i in range(5):
print('---1---')
yield b
print('---2---')
a, b = b, a+b
print('---3---')
print('----func end----')
g = createNums()
# print(g) # 生成器对象,<generator object createNums at 0x000001ECF8F81CA8>
'''程序从上至下执行,函数调用createNums(),打印func start,打印---1---,到达yield,next相当于触发了yield,
将b返回给了next(g) '''
print(next(g)) # ----func start----,---1---,1
print(next(g)) # 遇到第二个next(g),会接着上一次yield下面的代码继续执行,---2---,---3---,---1---,1
print(next(g)) # ---2---,---3---,---1---,2,改变的是b的值
小结:一旦有yield就会将生成的值进行返回到第一个next()处,继续出现next()时不会从头开始运行代码,而是接着yield下面的代码继续执行,直到又遇到yield,将后面的值返回到第二个next()处。
也可以通过for循环全部取出
# 斐波那契数列:除了第一个和第二个数以外,任何一个数都是前两个数的和
def createNums():
print('----func start----')
a, b = 0, 1
for i in range(5):
print('---1---')
yield b
print('---2---')
a, b = b, a+b
print('---3---')
print('----func end----')
g = createNums()
for i in g:
print(i)
yield与return的区别
return:函数的返回值,当函数代码执行到return时,就退出了函数,后面的代码都不会再执行。
yield:是将函数变为生成器的关键字,将值返回到next(),再遇到下一个next()时,会接着上一次执行的代码继续执行。
send()
send() 和 next() 一样,都能让生成器继续往下走一步(遇到 yield 返回),但 send() 能传一个值,这个值作为 yield 表达式整体的结果。
def test():
a1 = yield 'hell0'
yield a1
res = test()
print(next(res)) # hell0
print(res.send('world')) # world
程序从上至下执行,调用test(),在 a1 = yield 'hell0’的赋值运算中,先执行右边的yield ‘hello’,停到了此处,还没有执行赋值给a1的操作。所以第一个print返回的是hello。执行第二个print语句,send()跟next()一样,让代码继续执行,此时相当于a1 = ‘world’,到第二个yield处,yield a1,将a1返回到send()处。再有一个send() 的话就会报错,因为代码中只有两个yield,已经返回完了,且代码没有循环,那么再使用yield或者send时就超出了,会报错。(StopIteration)
res = test()
print(res.send('world'))
print(res.send(None)) # hello ,相当于next()
在上面的代码中,如果直接用send()就会报错,因为send不能使用非空的值作为启动生成器的对象。可以使用’None’,相当于next()。
迭代器与生成器总结
- 生成器能做到迭代器能做的所有事
- 因为生成器自动创建了 iter() 和 next() 方法,生成器显得简洁,而且高效。