第十二章:迭代器与生成器
迭代器
可迭代对象
1.可迭代对象
对象内置有 __iter__ 方法的都称为可迭代对象
"""
1.内置方法 通过点的方式能够调用的方法
2.__iter__ 双下 iter 方法
"""
2.可迭代对象的范围
不是可迭代对象
int float bool 函数对象
是可迭代对象
str list dict tuple set 文件对象
3.可迭代的含义
"""
迭代:更新换代(每次更新都必须依赖上一次的结果)
eg:手机 app 更新
"""
可迭代在 python 中可以理解为是否支持 for 循环
迭代器对象
1.迭代器对象
是由可迭代对象调用 __iter__ 方法产生的
迭代器对象判断的本质是看是否内置有 __iter__ 和 __next__
2.迭代器对象的作用
提供了一种不依赖于索引取值的方式
正因为有迭代器的存在,我们的字典、集合才能够被 for 循环
3.迭代器对象实操
s1 = 'hello' # 可迭代对象
res = s1.__iter__() # 迭代器对象
print(res.__next__()) # h 迭代取值 for循环的本质
print(res.__next__()) # e
print(res.__next__()) # l
print(res.__next__()) # l
print(res.__next__()) # o
print(res.__next__()) # 值已去完,则报错 StopIteration
# 一旦 __next__ 取不到值 会直接报错
4.注意事项
可迭代对象调用 __iter__ 会成为迭代器对象,迭代器对象如果还调用 __iter__ 不会有任何变化,还是迭代器对象本身
5.简写方式
iter() 等同于 __iter__
next() 等同于 __iter__
生成器
生成器
生成器的本质就是迭代器,还是内置有 __iter__ 和 __next__ 的迭代器对象
生成器与迭代器的区别
迭代器对象是解释器自动提供的
数据类型\文件对象>>>:迭代器对象
生成器对象是程序员编写出来的
代码、关键字>>>:迭代器对象(生成器)
生成器的表现形式
生成器函数
生成器函数 —— 本质上就是我们自己写得函数
生成器表达式
生成器函数:
含有 yield 关键字的函数就是生成器函数
特点:
调用函数的之后函数不执行,返回一个生成器
每次调用 next 方法的时候会取到一个值
直到取完最后一个,在执行 next 会报错
从生成器中取值的几个方法
1、next()
2、for
3、数据类型的强制转换:占用内存 print(list(g))
生成器对象
1、函数体代码中填写 yield 关键字,那么函数名加括号并不会执行函数体代码,会生成一个生成器对象(迭代器对象)
2、使用加括号之后的结果调用 __next__ 才会执行函数体代码
3、每次执行完 __next__ 代码都会停在 yield 位置,下次基于该位置继续往下找第二个 yield
4、yield 还有点类似于 return 可以返回返回值
5、yield 不能和 return 共用且需要写在函数内
# 只要含有 yield 关键字的函数都是生成器函数
# yield 不能和 return 共用且需要写在函数内
def test():
print(1)
yield 'a'
# 生成器函数:在执行之后会得到一个生成器作为返回值
ret = test() # 此处的 ret 是一个生成器而不是值了
print(ret) # <generator object test at 0x010D7DE0> 此处在同过原来的调用方法调用的是内存地址
print(ret.__next__()) # a 通过 .__next__() 来调用函数的输出内容
例子:
# 在取值时 会取到第一个 yield 时停止,然后等待下一次取值的动作开始,继续取值到下一个 yield
def my_next():
print('one')
yield 12, 123
print('two')
yield 23
print('three')
yield 34
ret = my_next() # 单独执行第一次时:one (12, 123)
# r1 = ret.__next__()
# print(r1) # (12, 123)
# r2 = ret.__next__() # __next__:即可以控制执行的位置
# print(r2) # 23
# r3 = ret.__next__()
# print(r3) # 34
for i in ret: # 使用 for 循环也同样可以
print(i) # 但 for 循环不可以控制执行位置
生成器函数进阶
关键字 send 的使用
# send 获取下一个值的效果和 next 基本一致
# 只是在获取下一个值的时候,给上一个 yield 的位置传递一个数据
# 使用 send 的注意事项
# 使用生成器取第一个值的时候,必须用 next 来取值
# 最后一个 yield 不能接受外部的值
def test(name, food=None):
print('{} 开饭了'.format(name))
while 1:
food = yield
print('今天有 {}'.format(food))
yield 123
ret = test('ysg') # ysg 开饭了
ret.__next__()
ret.send('鸡肉') # 今天有 鸡肉
ret.send('牛肉')
tt = ret.send('五花肉') # 今天有 五花肉
print(tt) # 123
生成器表达式
生成器表达式与列表推导式的区别
1、括号不一样(列表为:[],生成器为:())
2、返回的值不一样 === 几乎不占用内存
说白了就是生成器的简化写法
l1 = (i ** 2 for i in range(100)) # 生成器对象
print(l1) # <generator object <genexpr> at 0x01B97DE0>
for i in l1:
print(i)
生成器面试题
def add(n, i): # 普通函数 返回两个数的和 求和函数
return n + i
def test(): # 生成器
for i in range(4):
yield i
g = test() # 激活生成器
for n in [1, 10]:
g = (add(n, i) for i in g)
"""
# 在循环里面一直是生成式表达式,没有被调用,所以 for 循环结束后 n = 10
第一次for循环
g = (add(n, i) for i in g)
第二次for循环
g = (add(n, i) for i in (add(n, i) for i in g))
"""
res = list(g) # 这时 g 才真正的被调用 g = (add(10, i) for i in (add(10, i) for i in g))
print(res)
#A. res=[10,11,12,13]
#B. res=[11,12,13,14]
C. res=[20,21,22,23]
#D. res=[21,22,23,24]
'''不用深入研究 大致知道起始数即可'''
自定义生成器对标range功能
方法一
# 可优化点:interval=0 改为 interval=1 即可
def my_range(start_num, end_num=None, interval=0):
if not end_num:
start_num = 0
end_num = start_num
while start_num < end_num:
if interval:
if start_num < end_num:
yield start_num
start_num += interval
else:
if start_num < end_num:
yield start_num
start_num += 1
for i in my_range(10, 15, 3):
print(i)
方法二
def my_range(start_num, end_num=None, step=1):
# 判断end_num是否有值 没有值说明用户只给了一个值 起始数字应该是0 终止位置应该是传的值
if not end_num:
end_num = start_num
start_num = 0
while start_num < end_num:
yield start_num
start_num += step
for i in my_range(10, 15, 3):
print(i)
索引取值与迭代取值的差异
l1 = [11, 22, 33, 44, 55]
1.索引取值
可以任意位置任意次数取值
不支持无序类型的数据取值
2.迭代取值
只能从前往后依次取值无法后退
支持所有类型的数据取值(无序有序)
ps:两者的使用需要结合实际应用场景