Hello,大家好。本期来和大家一起学习一下迭代器和生成器的相关知识。
可迭代对象
想要学习迭代器,首先我们要知道什么是可迭代对象(iterable)。
概念
凡是内置有__iter__方法的对象都称之为可迭代对象。
可迭代对象判断
数值类型
不是可迭代对象。
a = 1
print(a.__iter__)
# AttributeError: 'int' object has no attribute '__iter__'
字符串类型
是可迭代对象。
c = 'hello'
print(c.__iter__())
print(c.__next__())
# <str_iterator object at 0x000002B860ECFFD0>
# AttributeError: 'str' object has no attribute '__next__'
列表类型
是可迭代对象。
d = ['a','c']
print(d.__iter__())
print(d.__next__())
# <list_iterator object at 0x00000238A05BFFD0>
# AttributeError: 'list' object has no attribute '__next__'
集合类型
是可迭代对象。
g = {1,2,3}
print(g.__iter__())
print(g.__next__())
# <set_iterator object at 0x0000025A8EB796C0>
# AttributeError: 'set' object has no attribute '__next__'
元组类型
是可迭代对象。
t = ('a','b','c')
print(t.__iter__())
print(t.__next__())
# <tuple_iterator object at 0x000001FE4ADBFFD0>
# AttributeError: 'tuple' object has no attribute '__next__'
字典类型
是可迭代对象。
dic = {'x':1,'y':2,'z':3}
print(dic.__iter__())
print(dic.__next__())
# <dict_keyiterator object at 0x000001F7FF95A4F0>
# AttributeError: 'dict' object has no attribute '__next__'
由此我们可以发现,只要是容器都有此__iter__方法。
迭代器
概念
执行可迭代对象下的__iter__方法,返回的值就是一个迭代器。
示例:
dic = {'x': 1, 'y': 2, 'z': 3} # 可迭代对象
iter_dic = dic.__iter__() # 可迭代对象变成迭代器
print(iter_dic) # <dict_keyiterator object at 0x0000019387512270>
print(iter_dic.__next__()) # x
print(iter_dic.__next__()) # y
print(iter_dic.__next__()) # z
print(iter_dic.__next__()) # 报错 StopIteration 应该被当成一种结束信号,代表迭代器取干净了
作用
迭代器提供了一种通用的且不依赖于索引的迭代取值方式的功能。
迭代器与可迭代对象的区别
可迭代对象:
一定有__iter__()方法,不一定有__next__()方法。
迭代器:
既内置有__iter__()方法,迭代器执行__iter__()方法得到的仍然是迭代器本身。
又内置有__next__()方法,迭代器执行__next__()方法可以不依赖索引取值。
注意:迭代器一定是可迭代的对象,而可迭代的对象却不一定是迭代器
误区
迭代器是一个容器,当取完其中的值后,就不能继续了,否则会报错。
# 01
l = [1, 2, 3]
print(l.__iter__().__next__()) # 1
print(l.__iter__().__next__()) # 1 此时生成了一个新的迭代器,又基于新的取值
# 02
l = [1, 2, 3]
print(l.__iter__().__next__()) # 1
iter_l = l.__iter__()
print(iter_l.__next__()) # 1 此时生成了一个新的迭代器,又基于新的取值
print(iter_l.__next__()) # 2
print(iter_l.__next__()) # 3
print(iter_l is iter_l.__iter__().__iter__().__iter__()) # True
for循环
本质
有没有一种好的方法?
1.自动把可迭代对象变成迭代器。
2.自动获取迭代器next的值。
3.next最后不报错。
因此for本质应该称之为迭代器循环。
运行机制
第一步:先调用in后面那个对象的__iter__()方法,将其变成一个迭代器.
如果是 迭代器 iter()可以变成迭代器 ——老的迭代器
如果是 可迭代对象 iter()可以变成迭代器 ——新的迭代器
第二步:调用next(迭代器),将得到的返回值赋值给变量名 i.
第三步:循环往复直到next(迭代器)抛出异常,for会自动捕捉异常StopIteration然后结束循环.
示例:
l = [1, 2, 3]
iter = l.__iter__()
for k in iter:
print(k)
print(iter.__iter__().__next__()) #基于老的迭代器,值已经取完,所以报错。
'''
1
2
3
StopIteration
'''
迭代器总结
优点:
提供一种通用的且不依赖于索引的迭代取值方式
同一时刻在内存中只存在一个值,更节省内存
缺点:
取值不如按照索引的方式灵活,(不能取指定的某一个值,而且只能往后取)
无法预测迭代器的长度
生成器
概念
生成器就是一种自定义的迭代器,本质就是迭代器。
特点:
但凡函数内包含yield关键字,调用函数时不会执行函数体代码。
调用函数时会得到一个返回值,该返回值就是生成器对象。
示例:
def func():
print('======1')
yield 1
print('======2')
yield 2
print('======3')
g = func()
g.__next__() # g 既有__iter__()方法,又有__next()方法
print(g) # 生成器,本质就是迭代器<generator object func at 0x000001A50359DF20>
print(g is g.__iter__().__iter__())
# 会触发函数的执行,直到碰到一个yield停下来,并且将yield后的值当作本次next的结果返回
print(g.__next__())
print(g.__next__())
'''
======1
<generator object func at 0x000001F6B7876C10>
True
======2
2
StopIteration
'''
yield总结
1.yield提供了一种自定义迭代器的解决方案。
2.yield可以保存函数的暂停的状态。
3.yield只能在函数内使用。
yield与return的异同
1.相同点:都可以返回值,值的类型与个数都没有限制。
2.不同点:yield可以返回多次值(暂停),而return只能返回一次值函数就结束了(结束)。
生成器计算斐波拉契数列
'''
定义一个生成器,这个生成器可以生成10位斐波拉契数列,得到斐波拉契数列
斐波那契数列:数列中每一个数的值都等于前两个数相加的值 [1, 1, 2, 3, 5, 8, 13, 21, 34, 55.........])
'''
def run(n):
i, a, b = 0, 1, 1
while i < n:
yield a # 第一是a 1 b 1 第二次 a 1 b 2 第三次 a 2 b 3 第四次 a 3 b 5 第五次 a 5 b 8
a, b = b, a + b
i += 1
my_run = run(10)
print(my_run) # <generator object run at 0x000001B9BACC24A0>
print(list(my_run)) # [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] 相当与把 my_run里面的值取完了
print('自定义迭代器(生成器)的for循环')
for i in my_run:
print(i) # 基于老的迭代器,没有数据可取
for i in my_run:
print(i) # 基于老的迭代器,没有数据可取
'''
<generator object run at 0x00000287FFFC9F90>
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
自定义迭代器(生成器)的for循环
'''
生成器.send()传值
了解 即可。
# 千与千寻
# 函数可不可以不断传值?
# yield的表达式形式的应用: x=yield
def run1(name):
print('千与千寻%s准备开吃' % name)
food_list = []
while True:
food = yield food_list
print('%s吃了%s' % (name, food))
food_list.append(food)
g = run1('无脸男')
print(g) # <generator object run1 at 0x000002DB9AB824A0>
res1 = next(g) # 千与千寻无脸男准备开吃
print(res1) # []
res2 = g.send('黄金') # 不仅仅把数据送进去赋值给food 还执行了next方法
print(res2) # ['黄金'] 返回的是 yield 后面的数据
res3 = g.send('黄金') # 不仅仅把数据送进去赋值给food 还执行了next方法
print(res3) # ['黄金', '黄金']
res3 = g.send('黄金') # 不仅仅把数据送进去赋值给food 还执行了next方法
print(res3) # ['黄金', '黄金', '黄金']
res3 = g.send('11111黄金') # 不仅仅把数据送进去赋值给food 还执行了next方法
print(res3) # ['黄金', '黄金', '黄金', '11111黄金']
生成器计算阶乘
'''
用生成器来计算1!+2!+3!+4!+......10!的和
'''
# 阶层
# 1! = 1
# 2! = 2*1
# 3! = 3*2*1
# 4! = 4*3*2*1
# 计数变量i
def func(n): # n 的阶层
i = 1 # 计数的变量 第一次 i 1
j = 1
while i <= n:
yield j # 第一次 j 1
i += 1 # 计数自增1 第二次 i 2 第三次 i 3 第四次 i 4 第5 次 i 5
j = j * i # 第二次 j 2 第三次 j 1*2 * 3 = 6 第四次 j 1 *2 * 3 * 4 = 24 第5 次 1 *2 * 3 * 4 * 5
a = func(10)
print(a) # <generator object func at 0x0000018159932510>
print(list(a)) # [1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]
print(sum(list(a))) # 0 # 基于老的迭代器,没有数据可取
更多用法,欢迎小伙伴后台留言哦。