Python 生成器与它的 send,throw,close 方法

cellchat安装 cellchat 阅读详情

Python 生成器与它的 send,throw,close 方法

转载请注明出处:https://blog.csdn.net/jpch89/article/details/87036970



0. 参考资料


1. 生成器简介

1.1 生成器的定义方式

Python 中,定义生成器有两种方式:

  • 生成器函数
    即内含 yield 关键字的函数。
    调用该函数,会得到一个生成器对象。
  • 生成器表达式
    把列表推导式的方括号改成圆括号,就成了生成器表达式。

举例如下:

from collections.abc import Generator


# 生成器函数
def fib(n):
    a, b = 0, 1
    i = 0
    while i < n:
       yield b
       a, b = b, a + b
       i += 1

print(isinstance(fib(1), Generator))
"""
True
"""

# 生成器表达式
g = (i for i in (1, 2, 3))
print(isinstance(g, Generator))
"""
True
"""


1.2 生成器与迭代器的关系

所有的生成器都是迭代器,因为生成器对象都有 __iter____next__ 方法,且 __iter__ 方法返回自身。

验证方式一:使用 isinstance 和抽象基类 collections.abc.Iterator 验证。

>>> from collections import Iterator
__main__:1: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
>>> from collections.abc import Iterator
>>> g = (i for i in '嘿嘿嘿')
>>> isinstance(g, Iterator)
True

上面的代码证明了生成器是迭代器 Iterator 的实例。

补充:

  • 注意 3.7 版本如果直接从 collections 模块中导入抽象基类,控制台会有废弃警告,并提示在 3.8 中将完全禁止这种写法,只能写成 from collections.abc import 抽象基类
  • 然而在 3.6from collections import Iterator 还是完全没有问题的。

验证方式二:使用 dir(g) 查看生成器 g 内部方法,并使用 iter(g) 检查返回值。

>>> dir(g)
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
>>> id(g)
2161748426448
>>> id(iter(g))
2161748426448
>>> iter(g) is g
True

可见生成器对象 g 中有 __iter____next__ 方法,并且 __iter__ 方法返回自身。

验证方式三:使用 issubclass 判断 Generator 是否是 Iterator 的子类。

>>> from collections.abc import Generator
>>> from collections.abc import Iterator
>>> issubclass(Generator, Iterator)
True

验证方式四:使用 __bases__ 属性,查看 Generator 父类,注意这个得到的是直接父类,祖先类并不会显示出来。

>>> Generator.__bases__
(<class 'collections.abc.Iterator'>,)

验证方式五:使用 __mro__ 属性,查看方法解析顺序元组。与 __bases__ 属性不一样的是,它会按照 C3 算法显示整个继承树。

>>> Generator.__mro__
(<class 'collections.abc.Generator'>, <class 'collections.abc.Iterator'>, <class 'collections.abc.Iterable'>, <class 'object'>)

2. 生成器对象的专属方法

在上节我们看到,生成器都是迭代器,但是生成器还有一些专属方法,都有哪些呢?可以通过差集运算来查看。

>>> set(dir(Generator)) - set(dir(Iterator))
{'close', 'send', 'throw'}

所以我们下面就来介绍这三个生成器对象的专属方法:

  • send
  • throw
  • close

2.1 生成器的 send 方法

2.1.1 参考帮助文档
>>> help(g.send)
Help on built-in function send:

send(...) method of builtins.generator instance
    send(arg) -> send 'arg' into generator,
    return next yielded value or raise StopIteration.

2.1.2 send 方法详解

generator.send(value)

  • 作用:向生成器发送一个值,随后恢复执行。
  • value 参数是 send 方法向生成器发送的值,这个值会作为当前所在的 yield 表达式的结果。
  • 随后生成器恢复执行,直到下一个 yield,把它后面的值作为 send 方法的结果返回。
    如果恢复执行后再也没有 yield 语句,生成器退出,并抛出 StopIteration 异常。
  • 如果一开始使用 send 启动生成器,必须使用 None 作为参数,因为一开始没有可以接收值的 yield 表达式。

个人理解
send 三部曲:发送值、恢复执行、返回值
send 就像是升级版的 next,比起 next 它多了发送值到生成器内的功能。
next 只有两步:恢复执行、返回值
g.send(None)next(g) 等价,也就是说,发送一个 None 相当于省去了发送值的这一步骤。

举一个例子:

import sys


def gen():
    x = yield 1
    print('x:', x)
    y = yield 2
    print('y:', y)


# 得到生成器对象 g
g = gen()

# 启动生成器(也叫激活生成器、预激生成器)
# 参数一定是 None,否则报错
# TypeError: can't send non-None value to a just-started generator
ret = g.send(None)
# 或者写成 next(g),这是激活生成器的推荐写法
print('第一次 yield 的返回值:', ret)
"""
第一次 yield 的返回值: 1
"""

print()
ret = g.send('测试')
print('第二次 yield 的返回值:', ret)
"""
x: 测试
第二次 yield 的返回值: 2
"""

print()
try:
    ret = g.send(999)
except StopIteration:
    exc_type, exc_value, exc_tb = sys.exc_info()
    print('异常类型:%s' % exc_type)
    print('异常值:%s' % exc_value)
    print('异常追踪信息:%s' % exc_tb)
"""
y: 999
异常类型:<class 'StopIteration'>
异常值:
异常追踪信息:<traceback object at 0x000001B97EC00308>
"""


2.2 生成器的 throw 方法

2.2.1 参考帮助文档
>>> help(g.throw)
Help on built-in function throw:

throw(...) method of builtins.generator instance
    throw(typ[,val[,tb]]) -> raise exception in generator,
    return next yielded value or raise StopIteration.

2.2.2 throw 方法详解

generator.throw(type[, value[, traceback]])

  • 作用:在生成器暂停的地方抛出类型为 type 的异常,并返回下一个 yield 的返回值。
  • 如果生成器函数没有捕获并处理传入的异常,或者说抛出了另一个异常,那么该异常会被传递给调用方。
  • 如果生成器退出时还没有 yield 新值,则会抛出 StopIteration 异常。

第一种情况:捕获并处理传入的异常,得到下一个 yield 的返回值。

def gen():
    n = 0
    while True:
        try:
            yield n
            n += 1
        except ZeroDivisionError:
            print('捕获到了 ZeroDivisionError')
            print('此时的 n 为:%s' % n)

g = gen()
ret = next(g)
print('第一次 yield 的返回值:%s' % ret)
"""
第一次 yield 的返回值:0
"""

print()
ret = g.throw(ZeroDivisionError)
print('第二次 yield 的返回值:%s' % ret)
"""
捕获到了 ZeroDivisionError
此时的 n 为:0
第二次 yield 的返回值:0
"""

print()
ret = next(g)
print('第三次 yield 的返回值:%s' % ret)
"""
第三次 yield 的返回值:1
"""

注意:

  • 之所以第二次 yield 的返回值还是 0,是因为在第一次 yield 的地方抛出了 ZeroDivisionError 异常,而该异常被 except 捕获,跳过了 n += 1 的步骤。
    except 异常处理器中也可以看到,n 并没有改变,仍然是 0
  • 可以看到,如果通过 throw 传入的异常被捕获的话,生成器能够恢复执行直到下一个 yield

第二种情况:没有捕获并处理 throw 传入的异常,异常会回传给调用方。

import sys

def gen():
    n = 0
    while True:
        yield n
        n += 1

g = gen()
ret1 = next(g)
print('第一次 yield 的返回值:%s' % ret1)
"""
第一次 yield 的返回值:0
"""

print()
try:
    ret2 = g.throw(ZeroDivisionError)  # ret2 并没有收到任何值
except ZeroDivisionError:
    print('调用方捕获到 ZeroDivisionError 异常')
    print(sys.exc_info())
"""
调用方捕获到 ZeroDivisionError 异常
(<class 'ZeroDivisionError'>, ZeroDivisionError(), <traceback object at 0x0000028E8AA10148>)
"""

print()
# 因为赋值没有发生就抛出了异常,所以变量 ret2 还不存在
try:
    print(ret2)
except NameError:
    print('捕获到了 NameError')
    print(sys.exc_info())
"""
捕获到了 NameError
(<class 'NameError'>, NameError("name 'ret2' is not defined"), <traceback object at 0x000001C624DB0248>)
"""

print()
print('尝试再次从生成器中获取值')
print(next(g))
"""
尝试再次从生成器中获取值
Traceback (most recent call last):
  File "test.py", line 41, in <module>
    print(next(g))
StopIteration
"""

注意:

  • 对于已经通过抛出异常而退出的生成器再使用 next(g) 会持续抛出 StopIteration 异常。

第三种情况:生成器退出时没有 yield 新值,会抛出 StopIteration 异常。

import sys

def gen():
    try:
        # 注意是在当前暂停的 yield 处抛出异常
        # 所以要在这里捕获
        yield 1
    except Exception as e:
        print('在生成器内部捕获了异常')
        print(e.args)
        print('处理完毕,假装什么也没发生')
        print()

    # yield 2

g = gen()
print(next(g))
"""
1
"""

print()
g.throw(TypeError, '类型错误哟~')
"""
在生成器内部捕获了异常
('类型错误哟~',)
处理完毕,假装什么也没发生

Traceback (most recent call last):
  File "test.py", line 23, in <module>
    g.throw(TypeError, '类型错误哟~')
StopIteration
"""

注意:

  • 虽然捕获并处理了 throw 传入的异常,但是由于处理完之后生成器没有后续语句而退出运行,而且并没有 yield 新值,所以会自动抛出一个 StopIteration 异常。
  • 如果把 yield 2 注释打开,则不会抛出 StopIteration 异常,因为此时生成器暂停并返回了 2
  • 如果 try 捕获的是 yield 2,那么实际上 TypeError('类型错误哟~') 会被传递到顶层调用方。因为 throw 是在当前暂停处抛出异常,也就是 yield 1 语句。

2.3 生成器的 close 方法

2.3.1 参考帮助文档
>>> help(g.close)
Help on built-in function close:

close(...) method of builtins.generator instance
    close() -> raise GeneratorExit inside generator.

2.3.2 close 方法详解

generator.close()

  • 作用:在生成器函数暂停的地方抛出一个 GeneratorExit 异常。
  • 这并不等价于 generator.throw(GeneratorExit),后面会说原因。
  • 如果生成器抛出 StopIteration 异常(不管是由于正常退出还是因为该生成器已经关闭),或者抛出 GeneratorExit 异常(不捕获该异常即可),close 方法不传递该异常,直接返回到调用方。而生成器抛出的其他异常会传递给调用方。
  • GeneratorExit 异常的产生意味着生成器对象的生命周期已经结束,因此生成器方法后续语句中不能再有 yield,否则会产生 RuntimeError。(而 throw 方法是期待一个 yield 返回值的,如果没有,则会抛出 StopIteration 异常。)
  • 对于已经正常退出或者因为异常退出的生成器对象,close 方法不会进行任何操作。

第一种情况:不捕获 GeneratorExit 异常,close 方法返回调用方,不传递该异常。

def gen():
    print('下面 yield 1')
    yield 1
    print('下面 yield 2')
    yield 2

g = gen()
next(g)
g.close()
"""
下面 yield 1
"""

print()
next(g)
"""
Traceback (most recent call last):
  File "test.py", line 15, in <module>
    next(g)
StopIteration
"""

注意:对已经关闭的生成器对象使用 next 会抛出 StopIteration 异常。

第二种情况:生成器自然退出抛出 StopIteration 异常,该异常不会传递给调用方,close 方法正常返回。

def gen():
    try:
        yield 1
    except GeneratorExit:
        print('捕获到GeneratorExit')
    print('生成器函数结束了')

g = gen()
print(next(g))
g.close()
"""
1
捕获到GeneratorExit
生成器函数结束了
"""

第三种情况:在 GeneratorExit 抛出后还有 yield 语句,会产生 RuntimeError。另外生成器对象被垃圾回收时,解释器会自动调用该对象的 close 方法(PEP 342),这意味着最好不要在相应的 exceptfinally 中写 yield 语句,否则不知道什么时候就会抛出 RuntimeError 异常。

def gen():
    try:
        yield 1
    except GeneratorExit:
        print('捕获到 GeneratorExit')
        print('尝试在 GeneratorExit 产生后 yield 一个值')
        yield 2

    print('生成器结束')


g = gen()
next(g)
g.close()
"""
捕获到 GeneratorExit
尝试在 GeneratorExit 产生后 yield 一个值
Traceback (most recent call last):
  File "test.py", line 14, in <module>
    g.close()
RuntimeError: generator ignored GeneratorExit
"""

一种防止抛出 RuntimeError 的安全生成器写法:设置一个布尔标识。

def safegen():
    yield 'so far so good'
    closed = False
    try:
        yield 'yay'
    except GeneratorExit:
        closed = True
        raise
    finally:
        if not closed:
            yield 'boo'

第四种情况:对已经关闭的生成器对象调用 close() 方法,不会进行任何操作。

def gen():
    yield 1
    print('我不会被执行')
    print('因为在 yield 1 就抛出了 GeneratorExit 异常')
    print('未经捕获的 GeneratorExit 异常不会传递')
    print('返回执行权给 close 的调用方')

g = gen()
g.close()
g.close()
g.close()  # 多次调用 close,什么效果都没有

补充GeneratorExit 异常只有在生成器对象被激活后,才有可能产生。

def gen():
    try:
        yield 1
    except GeneratorExit:
        print('捕获到 GeneratorExit')
        raise


g1 = gen()
next(g1)
g1.close()
"""
捕获到 GeneratorExit
"""

# 没有激活生成器,就不会触发 GeneratorExit 异常
print()
g2 = gen()
g2.close()
print('脚本运行完毕')
"""
脚本运行完毕
"""


完成于 2019.02.12

【WB32库开发】第9章 TIM1高级定时器——输出多路PWM PWM(Pulse Width Modulation)意为脉冲宽度调制,简称脉宽调制 。在工控行业,PWM信号可以用来调节电机转速、调节变频器以及BLDC电机驱动等;在LED照明行业,可以通过PWM来控制LED的亮暗变化;还可以通过PWM信号来控制无源蜂鸣器发出简单的声音等。 WB32也可输出PWM,需配置WB32上的定时器。 在WB32中有4个定时器,其中TIM1为高级定时器,TIMx(2-4)为通用定时器,这四个定时器均挂载在APB1总线上,每个定时器都可以输出4路PWM。 本章需要使用固件库TIM例程 阅读详情

相关推荐

python生成器(创建生成器,执行顺序,send方法)

python生成器generator):是一种特殊的迭代器,优势是可以在运行中途改变函数里的参数。 如何判断一个对象是否是一个生成器: from _collections_abc import generator list_num = (x for x in range(10)) print(isinstance(list_num, generator)) # True print(type(list_num)) # 生成器创建一(类似列表推导式): 列表推导式是 [ ],生成器( ) list_num = (x for x in range(10)) 生成器创建二: 函数内部使

Python生成器sendclosethrow方法详解

生成器函数使用yield关键字一次返回一个值,并在每次调用时暂停和恢复其状态。除了标准的迭代协议外,生成器对象还提供了一些额外的方法send()close()throw()

m0_65482549的博客 1184

光学领域:连续介质中束缚态驱动的手征超表面及其COMSOL仿真分析

内容概要:本文详细探讨了在连续介质中束缚态驱动下的平面手征超表面的研究,重点介绍了COMSOL Multiphysics仿真的应用。主要内容涵盖三次谐波的生成机制、本征手性BIC的作用、远场偏振图和手性透射曲线的分析、二维能带图、Q因子图和电场图的应用。此外,还介绍了如何在国际知名期刊Nature Communications (NC) 上复现研究成果,并强调了‘所见即所得’的设计理念,确保设计的直观性和可操作性。适合人群:从事光学研究的专业人士,尤其是对超表面设计和COMSOL仿真感兴趣的科研人员。使用场景及目标:适用于希望深入了解光物质相互作用机制的研究者,特别是那些希望通过仿真工具优化超表面设计的人群。目标是提高对三次谐波和手性光学响应的理解,提升超表面设计的精度和效率。其他说明:文中提供了详细的实验步骤和数据分析方法,有助于验证仿真结果的可靠性,并促进学术交流合作。

python中class用法实例

python中class用法实例 https://blog.csdn.net/u010551600/article/details/79126911 该程序的作用是找到studet.txt文件中,GPA最高的那名同学,并打印出他的信息。 程序运行的结果是: 首先建立一个student.txt文件,里面存储的是学生的信息 zhangsan 127 228 lisi 100 400 wangw...

shanhui123的博客 5175

python-生成器详解(2024年3月6日更新了内容)

生成器python中的一个对象(按照某种规律,来生成元素的对象),生成器不是列表,保存了产生元素的算法,同时会记录游标的位置(现在拿到第几个元素了),为了下次继续拿数据,而不是从头开始拿数据。可以通过一直调用next()方法获取值,这个对象不保存数据,每次调用会返回一个值,即做到了列表的好处,又不占用空间。...

python全栈 1万+

python throw_Python生成器sendclosethrow方法详解

Python生成器》一节中,详细介绍了如何创建一个生成器,以及生成器的基础用法。本节将在其基础上,继续讲解和生成器有关的一些方法Python生成器send()方法我们知道,通过调用 next() 或者 __next__() 方法,可以实现从外界控制生成器的执行。除此之外,通过 send() 方法,还可以向生成器中传值。值得一提的是,send() 方法可带一个参数,也可以不带任何参数(用 Non...

weixin_39564187的博客 534

python throw_Python 生成器它的 sendthrowclose 方法(转帖以及记录)

Python 生成器它的 sendthrowclose 方法生成器中,无论生成器是什么状态,都可以直接使用throwclose生成器这一块,对于next,send网上的介绍比较多,但对于throw以及close很多书上写的比较少,可能用的比较少,好在网上有很多介绍。以下是流畅的Pythonthrowclose的介绍:generator.throw(exc_type[, exc_va...

weixin_39927378的博客 569

Python生成器send, close, throw方法

send()方法 我们知道,通过调用 next() 或者 __next__() 方法,可以实现从外界控制生成器的执行。除此之外,通过 send() 方法,还可以向生成器中传值。 send() 方法可带一个参数,也可以不带任何参数(用 None 表示)。其中,当使用不带参数的 send() 方法时,它和 next() 函数的功能完全相同。 def intNum(): print("开始执行") for i in range(5): yield ...

hxxjxw的博客 579

python生成器方法throw_Python生成器sendclosethrow方法详解

Python生成器》一节中,详细介绍了如何创建一个生成器,以及生成器的基础用法。本节将在其基础上,继续讲解和生成器有关的一些方法Python生成器send()方法我们知道,通过调用 next() 或者 __next__() 方法,可以实现从外界控制生成器的执行。除此之外,通过 send() 方法,还可以向生成器中传值。值得一提的是,send() 方法可带一个参数,也可以不带任何参数(用 Non...

weixin_39739661的博客 252

python throw_python随用随学20200221-生成器中的send(),throw()close()方法

send()方法文档定义generator.send(value)Resumes the execution and “sends” a value into the generator function. The value argument becomes the result of the current yield expression. The send() method returns...

weixin_39766258的博客 263

python 生成器generator)的进阶=send close throw

生成器内部传值,并继续执行到下一个yield。close():终止生成器,抛,不能再迭代。:向生成器内部抛异常,可以被捕获处理,未捕获则终止。

qq_37805832的博客 523

[python]生成器的高级应用sendclosethrow方法

def gen_func(): #1. 可以产出值, 2. 可以接收值(调用方传递进来的值) html = yield &quot;http://projectsedu.com&quot; #这样写的话就是接收值 print(html) yield 1 #这样就是产出值给 调用方 return &quot;bobby&quot; #1. throw, close #1....

qq_38065133的博客 1008

揭秘Python生成器send方法:如何用它实现协程通信异常处理

掌握Python生成器send方法异常抛入技巧,实现高效协程通信错误控制。详解yield表达式如何接收外部值、响应throw注入异常,适用于异步任务调度状态机设计。理解双向通信机制,提升代码灵活性,值得收藏。

ByteShoal的博客 872

【11.5】生成器进阶--sendclosethrow方法

1.send方法 1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # 启动生成器有两种方法,next()send() 4 5 6 def gen_func(): 7 # 1.可以产出值 2.可以接收值(调用方传递的值) 8 html = yield 'http://proj...

weixin_30881367的博客 204

Python 高级编程和异步IO并发编程 --12_6 生成器进阶- sendclosethrow方法

生成器的一大特点:可以实现暂停,从生成器到协程,还有一些问题需要解决。 def gen_func(): yield 1 yield 2 yield 3 return "Tom" if __name__=="__main__": gen = gen_func() # 生成生成器对象 print(next(gen)) #1 # 生成器实现了迭代...

我思故我在! 487

python生成器

本文介绍了python生成器的原理、实现方式及内部方法生成器是迭代器的一种实现方式,主要依靠yield语句来返回值和传递值,可以使用next函数和send方法迭代生成器,可以使用close方法来提前结束迭代,可以使用throw方法在迭代过程中抛出错误来跳过某些迭代值。

爆笑蛙的博客 293

生成器方法

生成器的四种方法

weixin_39033358的博客 581

75、Python之函数式编程:生成器的核心方法及更多使用场景

简单总结一下,本文结合生成器的定义,简单介绍了__next__()方法send()方法throw()方法,以及close()方法的使用。基于send()方法可以实现生成器调用者之间双向通信的功能,简单列举了几个生成器的使用场景。

南宫理的日知录 907

python生成器

生成器 1.生成器 生成器generator)也是一种迭代器,在每次迭代时返回一个值,直到抛出 StopIteration 异常。它有两种构造方式: (1).生成器表达式 和列表推导式的定义类似,生成器表达式使用 () 而不是 [] ,比如: 可以看出生成器表达式无法像列表推导式那样直接输出,它和可迭代对象一样只能采用for循环调用next()函数 (2).生成器函数 含有 yield 关键字的函数,调用该函数时会返回一个生成器。 可以看到,上面的函数没有使用 return 语句返

weixin_55772194的博客 1万+
上一篇: Python 中的代码对象 code object 与 __code__ 属性
下一篇: Python 中 concurrent.futures 模块使用说明
团子大圆帅
博客等级 码龄11年 296粉丝 85原创
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值