第十二章:迭代器与生成器

迭代器

可迭代对象

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:两者的使用需要结合实际应用场景
posted @ 2022-10-19 07:25  亦双弓  阅读(148)  评论(0)    收藏  举报