生成器

生成器对象

概念
1.本质
还是内置有__iter__和__next__的迭代器对象

2.区别
迭代器对象是解释器自动提供的
数据类型\文件对象>>>:迭代器对象

生成器对象是程序员编写出来的
代码、关键字>>>:迭代器对象(生成器)

3.生成器的作用:
节省内存空间

4.创建生成器的基本语法
定义函数的时候,函数内部有yield关键字,就是生成器

函数调用之前就是普通函数,函数调用之后就变成了生成器(迭代器)

5.yeild的功能:
停顿代码
返回数据值
接收外部数据

def index():
    print('from index')
    print('hello world')
    yield
'''
    1.函数体代码中如果有yield关键字
    那么函数名加括号并不会执行函数体代码
    会生成一个生成器对象(迭代器对象)
'''
res = index()
'''2.使用加括号之后的结果调用__next__才会执行函数体代码'''
res.__next__()
# 输出结果是:
# from index
# hello world
'''3.每次执行完__next__代码都会停在yield位置 下次基于该位置继续往下找第二个yield'''

生成器一定是迭代器
迭代器不一定是生成器


def index():
    print('from index')
    print('hello world')
    yield 123
    print('second')
    yield 222
    print('three')
    yield 333
    print('four')
res = index()  # 此时就是生成器
r1 = res.__next__()  # 代码遇到yield关键字,会停住,夯住
>>> from index
>>> hello world
print(r1)
>>> 123
'''4.yield还有点类似于return 可以返回yield后面的值'''
r2 = res.__next__()
>>> second
print(r2)
>>> 222
r3 = res.__next__()  
>>> three
print(r3)
>>> 333
r4 = res.__next__()  
>>> four
>>> StopIteration  # 报错,没有找见yield

生成器的案例

实现range方法:
'''主要是学习思想'''
自定义生成器对标range功能(一个参数 两个参数 三个参数 迭代器对象)

for i in range(1, 10):
    print(i)

1.先写两个参数的
2.再写一个参数的
3.最后写三个参数

# 1.两个参数 
def my_range(start, stop):
    while start < stop:
        yield start
        start += 1

# 调用1:
# 用for循环调用
for i in my_range(1, 10):  # my_range(1, 10)是迭代器对象,也是可迭代对象
    print(i)
# 调用2:
# 每次单独调用
res = my_range(1, 10)
print(res.__next__())  # 写10次
# 调用3:
# 用for循环内部原理调用
res = my_range(1, 10)
while True:
    try:
        print(res.__next__())
    except StopIteration:
        break
# 2.三个参数  
def my_range(start, stop, step=1):
    while start < stop:
        yield start
        start += step

# 调用
for i in my_range(1, 10, 2):
    print(i)
# 3.一个参数   
def my_range(start, stop=None, step=1):
    # 判断stop是否有值,没有值说明用户只是给了一个值,起始数字应该是0,终止位置应该是传入的值
    if not stop:  # None是假
        stop = start
        start = 0
    while start < stop:
        yield start
        start += step

# 调用
for i in my_range(3):
    print(i)

# for i in my_range(1, 6):
#     print(i)

# for i in my_range(1, 7, 2):
#     print(i)

for i in my_range(10, 5):
    print(i)

# 4.也可以写成:   
def my_range(start, stop=None, step=1):
    if start < stop:
        if not stop:
            stop = start
            start=0
        while start < stop:  # 0<stop
            yield start
            start += step
    else:
        return '好好传值,别乱来'

# for i in my_range(1, 10):
for i in my_range(50, 10):
    print(i)

yield传值(了解)

3.1 yield传值

def eat():
    print('开始干饭')
    while True:
        food = yield
        print('开始吃%s' % food)
res = eat()  # 只是把函数变成了生成器
r1 = res.__next__()  # 取值,取到第一次遇到yield,然后停住
print(r1)
>>> 开始干饭
>>> None

传值用send()

res = eat()
r1 = res.__next__()
# send()的作用:1. 调用了__next__ 2. 传参数给yeild
res.send('臭豆腐')
res.send('面包')
res.send('米饭')
res.send('面条')
res.send('包子')
>>>
>>> 开始干饭
>>> 开始吃臭豆腐
>>> 开始吃面包
>>> 开始吃米饭
>>> 开始吃面条
>>> 开始吃包子

3.2 总结传值方式

'''
    不同的取值方式:
        (学习了3种)
        1.索引取值
        2.迭代取值
        3.yield传值(了解)
'''

1.索引取值

c = 'hello'
print(c[1])  # h

2.迭代取值

res = c.__iter__()
print(res.__next__())  # h
print(res.__next__())  # o

3.yield传值

3.3 迭代取值与索引取值的对比

索引取值

  1. 可以任意位置任意次数取值
  2. 不支持无序类型的数据取值:字典,集合不能索引取值

迭代取值

  1. 只能从前往后挨个取值,不能倒回去,除非从新生成一个新的迭代器
  2. 支持所有类型的数据取值(无序有序)

ps:两者的使用需要结合实际应用场景

yield与return的对比

yield

  1. 可以有返回值
  2. 函数遇到yield不会结束,只会'停住'
  3. yield关键字把函数变成了生成器,支持迭代取值了

return

  1. 可以有返回值
  2. 遇到return关键字直接结束函数运行

生成器表达式

先复习列表生成式:

l = [1, 2, 3, 4, 5, 6]
res = [i + 1 for i in l]
print(res)
>>> [2, 3, 4, 5, 6, 7]

生成器表达式说白了就是生成器的简化写法
作用:1.简化代码,2.节省内存

语法格式:

'''
    语法格式:
    res = (想每个元素进行的操作 for 变量 in 可迭代对象)
    
    操作:
    1.加字符串
    2.算术运算
    3.函数
'''
l = [1, 2, 3, 4, 5, 6]
res = (i + 1 for i in l)  # 生成器
print(res)
>>> <generator object <genexpr> at 0x000001A56CD14F68>
# 调用1:
print(tuple(res))
>>> (2, 3, 4, 5, 6, 7)
# 调用2:
print(res.__next__())  # print(next(res))
print(res.__next__())  # print(next(res))
print(res.__next__())  # print(next(res))
print(res.__next__())  # print(next(res))
print(res.__next__())  # print(next(res))
print(res.__next__())  # print(next(res))
'''
    注意:
        生成器如果不执行__next__,内部的代码不会执行
'''

笔试题(有难度)

def add(n, i):  # 普通函数 返回两个数的和  求和函数
    return n + i

def test():  # 有yield,是生成器1
    for i in range(4):
        yield i

g = test()  # 激活生成器1  # [0, 1, 2, 3]
print(g)
for n in [1, 10]:
    g = (add(n, i) for i in g)  # 新生成器2

    """
        第一次循环:n=1
            g = (add(n, i) for i in g)  # 重新将一个生成器赋值给g
            但是没有调用,所以没有执行
            如果循环只有一次,直接赋值,
            list(g)的输出结果是:[1,2,3,4]
        第二次循环:
            g = (add(n, i) for i in (add(n, i) for i in g))
            '''
            可以将上述语句写成:
            g1 = test()
            g2 = (add(n, i) for i1 in g1)
            g = (add(n, i) for i2 in g2)
            '''
            但是没有调用,所以没有执行
            如果循环有两次,赋值n=10, i1=[0,1,2,3],i2=[10,11,12,13]
            list(g)的输出结果是:[20,21,22,23]
        第三次循环
            g = (add(n, i) for i in (add(n, i) for i in (add(n, i) for i in g)))
            '''
            可以将上述语句写成:
            g1 = test()
            g2 = (add(n, i) for i1 in g1)
            g3 = (add(n, i) for i2 in g2)
            g = (add(n, i) for i3 in g3)
            '''
            但是没有调用,所以没有执行
            如果循环有三次,赋值n=20, i1=[0,1,2,3],i2=[20,21,22,23],i3=[40,41,42,43]
            list(g)的输出结果是:[60,61,62,63]
    """
    '''循环结束之后,才有调用语句,才开始赋值'''
res = list(g)  # 激活新生成器2
print(res)
>>> [20, 21, 22, 23]

res = next(g)
print(res)
res = next(g)
print(res)
res = next(g)
print(res)
res = next(g)
print(res)
>>> 20
>>> 21
>>> 22
>>> 23


# A. res=[10,11,12,13]
# B. res=[11,12,13,14]
# C. res=[20,21,22,23]
# D. res=[21,22,23,24]
'''不用深入研究 大致知道起始数即可'''
posted @ 2023-03-07 21:48  星空看海  阅读(24)  评论(0编辑  收藏  举报