返回顶部
首页 > 资讯 > 后端开发 > Python >[译]PEP 380--子生成器的语法
  • 506
分享到

[译]PEP 380--子生成器的语法

生成器语法PEP 2023-01-30 23:01:13 506人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

导语: PEP(python增强提案)几乎是 Python 社区中最重要的文档,它们提供了公告信息、指导流程、新功能的设计及使用说明等内容。对于学习者来说,PEP 是非常值得一读的第一手材料,学习中遇到的大部分难题,都能在 PEP 中找

导语: PEP(python增强提案)几乎是 Python 社区中最重要的文档,它们提供了公告信息、指导流程、新功能的设计及使用说明等内容。对于学习者来说,PEP 是非常值得一读的第一手材料,学习中遇到的大部分难题,都能在 PEP 中找到答案或者解决思路。

翻译了几篇 PEP,这么做的目的一方面是为了加强学习,另一方面也是为了锻炼自己的英文水平。Python 与 English,都是如此重要。翻译能将两者巧妙地结合起来,真是一举两得。

本文介绍了子生成器的语法,即 yield from 语法。其它与生成器相关的 PEP 有 3 篇,翻译的结果附在了本文末尾。若有对翻译感兴趣的同学,可在 GitHub 上关注下我创建的项目 peps-cn


PEP原文 : https://www.python.org/dev/peps/pep-0380/

PEP标题: Syntax for Delegating to a Subgenerator

PEP作者: GreGory Ewing

创建日期: 2009-02-13

合入版本: 3.3

译者 :豌豆花下猫(Python猫 公众号作者)

目录

  • 摘要
  • PEP接受
  • 动机
  • 提议
  • StopIteration 的增强
  • 形式语义
  • 基本原理
  • 重构原则
  • 结束方式
  • 作为线程的生成器
  • 语法
  • 优化
  • 使用StopIteration来返回值
  • 被拒绝的建议
  • 批评
  • 可选的提案
  • 附加材料
  • 参考资料
  • 版权

摘要

为生成器提出了一种新的语法,用于将部分的操作委派给其它的生成器。这使得一部分包含“yield”的代码段,可以被分离并放置到其它生成器中。与此同时,子生成器会返回一个值,交给委派生成器(delegating generator)使用。

当一个生成器再次 yield 被另一个生成器生成的值时,该语法还创造了一些优化的可能。

PEP接受

Guido 于 2011 年 6 月 26 日正式接受本 PEP。

动机

Python 的生成器是一种协程,但有一个限制,它只能返回值给直接的调用者。这意味着包含了 yield 的代码段不能像其它代码段一样,被拆分并放入到单独的函数中。如果做了这样的分解,就会导致被调用的函数本身成为一个生成器,并且必须显式地迭代这个生成器,以便重新 yield 它产生的所有值。

如果只关心生成值的过程,那么可以不费劲地使用如下的循环:

for v in g:
    yield v

但是,如果在调用send()throw()close()的情况下,要使子生成器与调用者正确地交互,就相当困难。如后面所说,必要的代码非常复杂,因此想要正确地处理所有特殊情况,将会非常棘手。

一种新的语法被提出来解决此问题。在最简单的用例中,它等同于上面的 for-循环,并且可以处理生成器的所有的行为,同时还能用简单而直接的方式进行重构。

提议

以下的新的生成器语法将被允许在生成器的内部使用:

yield from <expr>

其中 <expr\> 表达式作用于可迭代对象,从迭代器中提取元素。该迭代器会遍历到耗尽,在此期间,它直接向包含 yield from 表达式的调用者生成器(即“委托生成器”)生成和接收值。

此外,当该迭代器是一个生成器时,则此生成器可以执行 return 语句返回一个值,而该值将成为 yield from 表达式的值。

yield from 表达式的完整语义可通过生成器协议来描述如下:

  • 迭代器返回的任何值都直接传给调用者。
  • 使用 send() 发送给委托生成器的任何值都直接传给迭代器。如果发送的值是 None,则调用迭代器的 next() 方法。如果发送的值不是 None,则调用迭代器的 send() 方法。如果调用引发了 StopIteration,则恢复委托生成器。任何其它异常都会传递给委托生成器。
  • 除 GeneratorExit 以外,任何传给委托生成器的异常都会传给迭代器的 throw() 方法。如果调用引发 StopIteration,则恢复委托生成器。任何其它异常都会传递给委托生成器。
  • 如果传给委托生成器的是 GeneratorExit 异常,或者调用委托生成器的 close() 方法,则迭代器的 close() 方法会被调用(如果有)。如果调用时出现异常,则会传给委托生成器。否则的话,在委托生成器中抛出 GeneratorExit。
  • yield from 表达式的值是迭代器终止时引发的 StopIteration 异常的第一个参数。
  • 生成器里的 return expr 导致从生成器退出时引发 StopIteration(expr)。

StopIteration的增强功能

为方便起见,StopIteration 异常被赋予了一个 value 属性,来保存它的第一个参数,若无参数,则为 None。

正式的语义

本节使用 Python 3语法。

1、RESULT = yield from EXPR 语句等同于以下语句:

_i = iter(EXPR)
try:
    _y = next(_i)
except StopIteration as _e:
    _r = _e.value
else:
    while 1:
        try:
            _s = yield _y
        except GeneratorExit as _e:
            try:
                _m = _i.close
            except AttributeError:
                pass
            else:
                _m()
            raise _e
        except BaseException as _e:
            _x = sys.exc_info()
            try:
                _m = _i.throw
            except AttributeError:
                raise _e
            else:
                try:
                    _y = _m(*_x)
                except StopIteration as _e:
                    _r = _e.value
                    break
        else:
            try:
                if _s is None:
                    _y = next(_i)
                else:
                    _y = _i.send(_s)
            except StopIteration as _e:
                _r = _e.value
                break
RESULT = _r

2、在生成器中,return value 语句在语义上等同于 raise StopIteration(value) ,除了一点,当前返回的生成器中的 except 子句无法捕获该异常。

3、 StopIteration 异常的行为就像这样定义:

class StopIteration(Exception):

    def __init__(self, *args):
        if len(args) > 0:
            self.value = args[0]
        else:
            self.value = None
        Exception.__init__(self, *args)

基本原理

重构原则

上面提到的大多数语义,其背后的基本原理源于一种对生成器代码进行重构的愿望。即希望可以将包含一个或多个 yield 表达式的代码段,分离进一个单独的函数中(使用常规手段来处理作用域范围内的变量引用,等等),并通过 yield from 表达式来调用该函数。

在合理可行的情况下,这种复合而成的生成器的行为应该跟原始的非分离的生成器完全相同,包括调用 __next __() 、send()、throw() 和 close() 。

子迭代器(而非生成器)的语义被选择成为生成器案例的合理泛化(generalization)。

所提出的语义在重构方面具有如下限制:

  • 一个捕获了 GenetatorExit 却不重新抛出的代码块,不能在完全保留相同行为的情况下被分离出去。
  • 如果将 StopIteration 异常抛进了委托生成器中,则分离的生成器的行为跟原始代码的行为可能会不同。

由于这些用例几乎不存在,因此不值得为支持它们而考虑额外的复杂性。

结束方式

当在 yield from 处挂起时,并且使用 close() 方法显式地终止委托生成器时,关于是否要一并终止子迭代器,存在一些争议。一个反对的论据是,如果在别处存在对子迭代器的引用,这样做会导致过早结束它。

对非引用计数型的 Python 实现的考虑,导致了应该显式地结束的结论,以便在所有类型的 Python 实现上,显式地结束子迭代器与非重构的迭代器,能具有相同的效果。

这里做的假设是,在大多数用例中,子迭代器不会被共享。在子迭代器被共享的稀有情况下,可通过一个阻塞调用 throw() 和 close() 的装饰器来实现,或者使用除 yield from 以外的方法来调用子迭代器。

作为线程的生成器

使生成器能够 return 值的动机,还考虑到使用生成器来实现轻量级的线程。当以这种方式使用生成器时,将轻量级线程的计算扩散到许多函数上就会是合理的。人们希望能够像调用普通函数一样调用子生成器,传递给它参数并接收返回值。

使用提议的语法,像以下的表达式

y = f(x)

其中 f 是一个普通的函数,就可以被转化成一个委托调用

y = yield from g(x)

其中 g 是生成器。通过把 g 想象成一个普通的能被 yield 语句挂起的函数,人们可以推断出结果代码的行为。

当以这种方式把生成器作为线程使用时,通常人们不会对 yield 所传入或传出的值感兴趣。但是,也有一些例子,线程可以作为 item 的生产者或消费者。yield from 表达式允许线程的逻辑被扩散到所需的尽可能多的函数中,item 的生产与消费发生在任意的子函数中,并且这些 item 会自动路由到/去它们的最终来源/目的地。

对于 throw()close() ,可以合理地预期,如果从外部向线程内抛入了一个异常,那么首先应该在线程挂起处的最内部的生成器中引发,再从那里向外传递;而如果线程是从外部调用 close() 来终结的,那也应该从最内部往外地终止处于活动态的生成器链。

语法

所提出的特定语法被选中,像它的含义所暗示,并没有引入任何新的关键词,且清晰地突出了它与普通 yield 的不同。

优化

当存在一长串生成器时,使用专门的语法就为优化提供了可能性。这种生成器链可能存在,例如,当递归遍历树结构时。在链上传递 __next__() 的调用与 yield 返回值,可能造成 O(n) 开销,最坏情况下会是 O(n**2)。

可能的策略是向生成器对象添加一个槽(slot)来保存委派给它的生成器。当在生成器上调用 __next__() 或 send() 时,首先检查该槽,如果非空,则它引用的生成器将会被激活。如果引发了 StopIteration,该槽会被清空,并且主生成器会被激活。

这将减少一系列 C 函数调用的委托开销,并不涉及 Python 代码的执行。一种可能的增强方法是在循环中遍历整个生成器链,并直接激活最后一个生成器,尽管 StopIteration 的处理会比较复杂。

使用StopIteration来返回值

有多种方法可以将生成器的返回值传回。也有一些替代的方法,例如将其存储为生成器-迭代器对象的属性,或将其作为子生成器的 close() 方法的调用值返回。然而,本 PEP 提议的机制很有吸引力,有如下理由:

  • 使用泛化的 StopIteration 异常,可以使其它类型的迭代器轻松地加入协议,而不必增加额外的属性或 close() 方法。
  • 它简化了实现,因为子生成器的返回值变得可用的点与引发异常的点相同。延迟到任意时间都需要在某处存储返回值。

被拒绝的建议

一些想法被讨论并且拒绝了。

建议:应该有一些方法可以避免对__next__() 的调用,或者用带有指定值的 send() 调用来替换它,目的是支持对生成器作装饰,以便可以自动地执行初始的 __next__()

决议:超出本提案的范围。这种生成器不该与 yield from 一起使用。

建议:如果关闭一个子迭代器时,引发了带返回值的 StopIteration 异常,则将该值从 close() 调用中返回给委托生成器。

此功能的动机是为了通过关闭生成器,传信号给传入生成器的最后的值。被关闭的生成器会捕获 GeneratorExit ,完成其计算并返回一个结果,该结果最终成为 close() 调用的返回值。

决议:close() 与 GeneratorExit 的这种用法,将与当前的退出(bail-out)与清理机制的角色不兼容。这要求在关闭子生成器后、关闭一个委托生成器时,该委托生成器可以被恢复,而不是重新引发 GeneratorExit。但这是不可接受的,因为调用 close() 进行清理的意图,无法保证委托生成器能正确地终止。

通过其它方式,可以更好地处理向消费者告知(signal)最后的值的问题,例如发送一个哨兵值(sentinel value)或者抛入一个被生产者与消费者都认可的异常。然后,消费者可以检查该哨兵或异常,通过完成其计算并正常地返回,来作响应。这种方案在存在委托的情况下表现正确。

建议:如果 close() 不返回值,如果出现 StopIteration 中带有非 None 的值,则抛出一个异常。

决议:没有明确的理由如此做。忽略返回值在 Python 中的任何其它地方,都不会被视为错误。

批评

根据本提案,yield from 表达式的值将以跟普通 yield 表达式非常不同的方式得出。这意味着其它不包含 yield 表达式的语法可能会更合适,但到目前为止,还没有提出可接受的替代方案。被拒绝的替代品包括 call、delegate 和 GCall。

有人提议,应该使用子生成器中除 return 以外的某些机制,来处理 yield from 表达式的返回值。但是,这会干扰将子生成器视为可挂起函数的目的,因为它不能像其它函数一样 return 值。

有人批评,说使用异常来传递返回值是“滥用异常”,却没有任何具体的理由来证明它。无论如何,这只是一种实现的建议;其它机制可以在不丢失本提案的任何关键特性的情况下使用。

有人建议,使用与 StopIteration 不同的异常来返回值,例如 GeneratorReturn。但是,还没有令人信服的实际理由被提出,并且向 StopIteration 添加 value 属性减轻了从异常(该异常可能存在也可能不存在)中提取返回值的所有困难。此外,使用不同的异常意味着,与普通函数不同,生成器中不带值的 return,将不等同于 return None

可选的提案

之前已经提到了类似的提议,有些语法使用 yield * 而不是 yield from。虽然 yield * 更简洁,但是有争议的是,它看起来与普通的 yield 太相似了,可能在阅读代码时会忽视了其中的差异。

据作者所知,之前的提案只关注于 yield 产生值,因此遭受到了批评,即他们所替代的两行 for 循环并没有足够令人厌烦,不足以让人为新的语法辩护。通过处理完整的生成器协议,本提案提供了更多的好处。

附加材料

本提案的语法的一些用例已经被提供出来,并且基于上面概括的第一个优化的原型也已实现。

Examples and Implementation

可以从跟踪器问题的 issue 11682 中获得针对 Python 3.3 实现的升级版本。

参考资料

[1] Https://mail.python.org/pipermail/python-dev/2011-June/112010.html

[2] http://www.cosc.canterbury.ac.nz/greg.ewing/python/yield-from/

[3] http://bugs.python.org/issue11682

版权

本文档已经放置在公共领域。源文档:

https://github.com/python/peps/blob/master/pep-0380.txt

-------------(译文完)-------------

相关链接:

PEP背景知识 :学习Python,怎能不懂点PEP呢?

PEP翻译计划 :https://github.com/chinesehuazhou/peps-cn

[译] PEP 255--简单的生成器

[译] PEP 342--增强型生成器:协程

[译] PEP 525--异步生成器


公众号【Python猫】, 专注Python技术、数据科学和深度学习,力图创造一个有趣又有用的学习分享平台。本号连载优质的系列文章,有喵星哲学猫系列、Python进阶系列、好书推荐系列、优质英文推荐与翻译等等,欢迎关注哦。PS:后台回复“爱学习”,免费获得一份学习大礼包。

--结束END--

本文标题: [译]PEP 380--子生成器的语法

本文链接: https://lsjlt.com/news/181146.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • [译]PEP 380--子生成器的语法
    导语: PEP(Python增强提案)几乎是 Python 社区中最重要的文档,它们提供了公告信息、指导流程、新功能的设计及使用说明等内容。对于学习者来说,PEP 是非常值得一读的第一手材料,学习中遇到的大部分难题,都能在 PEP 中找...
    99+
    2023-01-30
    生成器 语法 PEP
  • [译] PEP 255--简单的生成器
    我正打算写写 Python 的生成器,然而查资料时发现,引入生成器的 PEP 没人翻译过,因此就花了点时间翻译出来。如果在阅读时,你有读不懂的地方,不用怀疑,极有可能是我译得不到位。若出现这种情况,我建议你直接阅读原文,最好也能将错误处...
    99+
    2023-01-30
    生成器 简单 PEP
  • Python 自然语言处理中的生成式模型:从文本生成到机器翻译
    文本生成模型 文本生成模型利用输入的语言信息来生成新的文本,使其看起来像自然语言。这些模型可以使用统计方法或基于神经网络的深度学习方法来训练。 预训练语言模型(如BERT、GPT-3)已在文本生成领域取得了重大进展。它们能够生成连贯且内容...
    99+
    2024-04-02
  • Cgo 生成的源无法在 MVC 上编译
    来到编程网的大家,相信都是编程学习爱好者,希望在这里学习Golang相关编程知识。下面本篇文章就来带大家聊聊《Cgo 生成的源无法在 MVC 上编译》,介绍一下,希望对大家的知识积累有所帮助,助力实...
    99+
    2024-04-05
  • Vue编译器实现代码生成方法介绍
    这里将讨论如何根据JavaScript AST生成渲染函数的代码,即代码生成。代码生成本质上是字符串拼接的艺术。需要访问JavaScript AST中的节点,为每一种类型的节点生成相...
    99+
    2023-01-05
    Vue代码生成 Vue代码生成方案
  • 将python生成的exe文件反编译成py文件的方法
    前言 闲来无事,就喜欢瞎折腾,之前用python打包过exe小工具,然后今天就突然想到,既然能打包,那就肯定能反编译成py文件,为了这个想法,就网上查资料,自己操作了一下,过程参考了文章: https://www.cnblogs.com/s...
    99+
    2023-09-13
    pycharm ide python
  • Python生成器的用法
    这篇文章主要讲解了“Python生成器的用法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python生成器的用法”吧!一、生成器在 Python 中,使用了 yield 的函数被称为生成器...
    99+
    2023-06-02
  • c语言中源文件编译后生成的文件是什么
    这篇文章主要介绍“c语言中源文件编译后生成的文件是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“c语言中源文件编译后生成的文件是什么”文章能帮助大家解决问题。c语言编译后生成“.OBJ”的二进制...
    99+
    2023-07-04
  • R语言matrix生成矩阵的方法
    主要介绍一下利用matrix函数和rep生成矩阵 在R语言中可以使用matrix()函数来创建矩阵,其语法格式如下: matrix(data=NA, nrow = 1, nco...
    99+
    2024-04-02
  • R语言生成随机数的方法
    这篇文章主要介绍R语言生成随机数的方法,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!1.概述作为一种语言进行统计分析,R有一个随机数生成各种统计分布功能的综合性图书馆。R语言可以针对不同的分布,生成该分布下的随机数。...
    99+
    2023-06-14
  • python 生成器生成杨辉三角的方法(必看)
    用Python写趣味程序感觉诺模2幌吕/p> #生成器生成展示杨辉三角 #原理是在一个2维数组里展示杨辉三角,空的地方用0,输出时,转化为' ' def yang(line): n,leng=0,2...
    99+
    2022-06-04
    生成器 必看 方法
  • python使用生成器的方法
    这篇文章主要介绍了python使用生成器的方法的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇python使用生成器的方法文章都会有所收获,下面我们一起来看看吧。尽可能使用生成器单机处理较大数据量时,生成器往往很...
    99+
    2023-06-27
  • 使用python批量生成insert语句的方法
    1.建表语句 2.目标insert语句 INSERT INTO `bidprcu_dic_a`( `DIC_ID`, `DIC_TYPE_CODE`, `DIC_TYP...
    99+
    2024-04-02
  • 生成器Generator的原理及用法
    本篇内容主要讲解“生成器Generator的原理及用法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“生成器Generator的原理及用法”吧!基本用法我们知道,...
    99+
    2024-04-02
  • Python语法糖生成同一类的相同实例?
    问题内容 假设我有这个简单的课程: class person: def __init__(self, name, id): self.name = name ...
    99+
    2024-02-09
  • c语言生成动态库的方法是什么
    在C语言中,生成动态库的方法一般是通过编译链接的方式来实现的。下面是一般的步骤: 编写源代码文件:首先编写需要生成动态库的源代码...
    99+
    2024-03-02
    c语言
  • Python中迭代器与生成器的用法
    一、迭代器(foreach) 1、可迭代的对象 内置有__iter__方法的都叫可迭代的对象。 Python内置str、list、tuple、dict、set、file都是可迭代对象...
    99+
    2024-04-02
  • HTML子选择器的语法是什么
    这篇文章主要介绍“HTML子选择器的语法是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“HTML子选择器的语法是什么”文章能帮助大家解决问题。 子选择器使用了...
    99+
    2024-04-02
  • CSS子选择器的语法是什么
    这篇文章主要讲解了“CSS子选择器的语法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“CSS子选择器的语法是什么”吧! 语法解释 您应该已经注意到...
    99+
    2024-04-02
  • python委派生成器的具体方法
    1、生成器函数包含yield from表达式。 2、在yield from表达式处暂停委派生成器,调用方可直接将数据发送给子生成器。 3、子生成器将输出值发送给调用方。 4、解释器会...
    99+
    2022-11-21
    python 委派生成器
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作