学习生成器


温故而知新, 重新看一下以前做的笔记。

生成器

什么是生成器?

生成器是一个特殊的迭代器(迭代器的抽象级更高)。

兼具迭代器的特性

  1. 惰性计算数据, 节省内存
    什么是惰性计算数据, 就是把数据 分成N份, 每次处理一份, 处理完成后丢弃掉, 每次都不需要考虑什么, 调用就可以获取一份数据, 用完就丢弃
  2. 能够记录状态, 并通过 next() 函数,访问下一个状态
  3. 具有可迭代特性
    但是如果打造一个自己的迭代器, 比较复杂, 所以就有了一个更加优雅的方式 ‘生成器’ , 所以说 生成器是一种迭代器, 但是 迭代器不一定是生成器
    什么是抽象级更高? 比如人是动物, 但是动物不一定是人, 因此动物的抽象级更高, 而人就更加具象化

    生成器的创建方式

    生成器表示式

    把列表推导式的 [] 修改成 () 即可
    # 列表推导式
    member = [i for i in range(1, 10) if i % 2 != 0]
    
    # 生成器,把列表推导式的 [] 改成 ()  即可
    sc = (i for i in range(1, 10) if i % 2 != 0)
    print(member)
    print("-----------")
    print(sc)
    
    
    运行效果如下
    [1, 3, 5, 7, 9]
    -----------
    <generator object <genexpr> at 0x0000022412A72B30>
    我们用列表推导式生成的那么长的数据, 有时候不一定有的完,所以可能会造成内存浪费, 因此我们就可以使用生成器, generator 是生成器的意思

    生成器函数

    生成器函数: 函数中包含 yield语句, 这个函数的执行结果就是”生成器”, yield value 是状态值, 当代码执行到yield 语句的时候, 会停下来记录状态值, 并且把 value 返回出去
    def test():
        print("come on")
        yield 1
        print("again")
        yield 2
        print("last")
        yield 3
        print("再来一次,后面没有yield")
        # yield 10086
    
    g = test()
    # <generator object test at 地址值>
    print(g)
    print(next(g))
    print("------")
    print(next(g))
    print("------")
    print(next(g))
    # 再来一次,就报错了, 补上 yield 10086 就不会了
    print("------")
    print(next(g))
    # # 即使补上 yield 10086, 这里也会报错,因为后面没有yield 返回值了,就不能继续使用next() 了
    # print("=====")
    # print(next(g))

生成器举例

也可以用在生成器函数中, 使用到for 循环来生成 field状态值

def test():
    print("come on")
    for i in range(10):
        print("doing")
        yield i
        print("action")

g = test()
print(next(g))
print("------")
print(next(g))
print("------")
print(next(g))

运行效果如下

come on
doing
0
------
action
doing
1
------
action
doing
2

生成器产生数据的方式

生成器具有可迭代性,  next() 函数,  等价于  生成器.__next__()   也可以用 for in  来遍历

生成器函数里的yield语句特点:遇到 yield就会停下来

def test():
    print("come on")
    for i in range(10):
        print("doing")
        result = yield i
        print("action")
        print("看看赋值结果 " + str(result))

g = test()
print(next(g))
print("------")
print(next(g))
print("------")
print(next(g))

运行效果如下

come on
doing
0
------
action
看看赋值结果 None
doing
1
------
action
看看赋值结果 None
doing
2

进程已结束,退出代码 0

这里也看出, 通过普通的赋值语句, 拿不到 yield 语句的 yield 状态值, yeid 的 返回值并不重要, 它只是起到一个阻断代码的作用

因此搭配 for in 来使用效果更好

def test():
    print("come on")
    for i in range(5):
        print("****第%s 次进来这里****" % (i+1))
        yield i
        print("action")
        print("第%s 次阻断代码" % (i+1))

g = test()
for i in range(5):
    # 等价于next(g)
    g.__next__()
#  这一行会报错StopIteration。 迭代器要复用,必须要回退,生成器也是属于迭代器
# g.__next__()


运行效果

come on
****第1 次进来这里****
action
第1 次阻断代码
****第2 次进来这里****
action
第2 次阻断代码
****第3 次进来这里****
action
第3 次阻断代码
****第4 次进来这里****
action
第4 次阻断代码
****第5 次进来这里****

生成器的特点之必须实例化才能使用

生成器必须要实例化才可以使用, 否则记录不了状态, 在第一个 yeild 语句就会停下来

def test():
    print("come on")
    for i in range(5):
        print("****第%s 次进来这里****" % (i+1))
        yield i
        print("action")
        print("第%s 次阻断代码" % (i+1))

test().__next__()
test().__next__()
test().__next__()

运行效果

come on
****第1 次进来这里****
come on
****第1 次进来这里****
come on
****第1 次进来这里****

生成器的send 方法

send 方法有一个参数,指定的是上一次被挂起的yield 语句的返回值;     
相比于.__next__() , 可以额外的给yield 语句传值
注意第一次调用 t.send(None)

以下是代码示范

def test():
    print("开始")
    #  遇到yield 语句就会停下来,所以赋值语句是没有执行到的
    result = yield 1
    print("看看赋值结果, %s" % result)
    result = yield 2
    print("看看赋值结果, %s" % result)
    result = yield 3
    print("看看赋值结果, %s" % result)
    result = yield 4
    print("看看赋值结果, %s" % result)
g = test()
g.__next__()
g.__next__()
# send 是给上一个yield 的返回值进行赋值, 此时结合代码来看是给yield 2 那里给赋值了
g.send("今宵酒醒何处")
g.__next__()

运行效果


开始
看看赋值结果, None
看看赋值结果, 今宵酒醒何处
看看赋值结果, None

send方法的注意事项

在没有启动过生成器的情况下, 首次启动生成器就使用 send() 方法, 发送None 是可以的, 发送None 在此时可以认为是启动了生成器,但是这个None并不会给yield 赋值.
但是此时发送其他值,则会报错的。 这里就不做代码演示了。

关闭生成器 g.close()

def test():
    print("开始")
    #  遇到yield 语句就会停下来,所以赋值语句是没有执行到的
    result = yield 1
    print("看看赋值结果, %s" % result)
    result = yield 2
    print("看看赋值结果, %s" % result)
    result = yield 3
    print("看看赋值结果, %s" % result)
    result = yield 4
    print("看看赋值结果, %s" % result)
g = test()
g.__next__()
g.__next__()
# send 是给上一个yield 的返回值进行赋值, 此时结合代码来看是给yield 2 那里给赋值了
g.send("今宵酒醒何处")
g.__next__()
# 关闭生成器后,还继续调用,也会报错StopIteration,  但是会把生成器的位置放在最后一个yield  了
g.close()
g.__next__()

运行效果

E:\pythonScript\Blog\venv\Scripts\python.exe E:/pythonScript/Blog/plance.py
Traceback (most recent call last):
  File "E:/pythonScript/Blog/plance.py", line 20, in <module>
    g.__next__()
StopIteration
开始
看看赋值结果, None
看看赋值结果, 今宵酒醒何处
看看赋值结果, None

进程已结束,退出代码 1

生成器的注意事项(1)遇到return 会停止并且抛出异常

遇到 return 就会停止, 并且也会抛出一个异常, 返回值都一起显示

def test():
    print("开始")
    #  遇到yield 语句就会停下来,所以赋值语句是没有执行到的
    result = yield 1
    print("看看赋值结果, %s" % result)
    # return 后面的就不会执行了
    return 100
    result = yield 2
    print("看看赋值结果, %s" % result)
    result = yield 3
    print("看看赋值结果, %s" % result)
    result = yield 4
    print("看看赋值结果, %s" % result)
g = test()
g.__next__()
next(g)

运行效果


Traceback (most recent call last):
  File "E:/pythonScript/Blog/plance.py", line 16, in <module>
    next(g)
StopIteration: 100
开始
看看赋值结果, None

(2)也可以用 for in 迭代器运行, 并且还可以迭代一次

def test():
    print("开始")
    #  遇到yield 语句就会停下来,所以赋值语句是没有执行到的
    result = yield 1
    print("看看赋值结果, %s" % result)
    # return 后面的就不会执行了

    result = yield 2
    print("看看赋值结果, %s" % result)
    result = yield 3
    print("看看赋值结果, %s" % result)
    result = yield 4
    print("看看赋值结果, %s" % result)
g = test()
for i in g:
    print(i)

运行效果

开始
1
看看赋值结果, None
2
看看赋值结果, None
3
看看赋值结果, None
4
看看赋值结果, None

用 for in 可以把最后一个 yield 语句后面的 语句给执行了, 不会像 g.next() 那样抛出异常. 自己根据情况选择。
因为生成器本身也是迭代器, 因此它只能够迭代使用一次。

但是如果想要这个生成器里的东西重复运行呢?

那就重新实例化一个生成器对象就可以了, 这样就可以继续运行了


文章作者: 陌上人如玉
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 陌上人如玉 !
  目录