当前位置: 首页 > Linux

【译文】PEP380--Subgenerator的语法

时间:2023-04-06 23:07:21 Linux

简介:PEP(PythonEnhancementProposal)几乎是Python社区最重要的文档。它们提供公告信息、指导流程、新功能设计和使用说明等内容。对于学习者来说,PEP是值得一读的第一手资料。学习中遇到的问题,大部分都可以在PEP中找到答案或解决方案。我翻译了一些PEP。这样做的目的一方面是为了加强自己的学习,另一方面也是为了锻炼自己的英语水平。Python和英语都很重要。翻译能将两者巧妙结合,真是一石二鸟。本文介绍了子生成器的语法,yieldfrom语法。生成器相关的PEP还有3个,翻译结果附在文末。如果你对翻译感兴趣,可以在Github上关注我的项目peps-cn。PEP原文:https://www.python.org/dev/pe...PEP标题:SyntaxforDelegatingtoaSubgeneratorPEP作者:GregoryEwing创建日期:2009-02-13合并版本:3.3译者:豌豆花下一篇cat(Pythoncat公众号作者)目录摘要PEP接受的动机提案增强的形式语义基本原理停止迭代材料参考版权摘要提出了一种新语法,用于生成器将其部分操作委托给其他生成器。这允许将包含“yield”的代码部分分开并放入其他生成器中。同时,子生成器会返回一个值给委托生成器使用。当生成器再次生成由另一个生成器生成的值时,此语法还为某些优化创造了可能性。PEP接受Guido于2011年6月26日正式接受了此PEP。动机Python的生成器是协程,但具有只能将值返回给直接调用者的限制。这意味着包含yield的代码段不能像其他代码段一样拆分并放入单独的函数中。进行这样的分解会导致被调用函数本身就是一个生成器,并且必须显式地迭代这个生成器,以便重新生成它生成的所有值。如果你只关心生成值的过程,你可以使用这样的循环而不会有太多麻烦:forving:yieldv然而,如果你想让子生成器在调用send()时,抛()和close()与调用者正确交互是相当困难的。正如后面提到的,必要的代码非常复杂,因此正确处理所有特殊情况将很棘手。一种新的语法被提出来解决这个问题。在最简单的用例中,它相当于上面的for循环,处理生成器的所有行为,同时以简单直接的方式进行重构。以下新的生成器语法被提议允许在生成器内部使用:yieldfrom其中表达式作用于可迭代对象,从迭代器中提取元素。迭代器遍历到耗尽,在此期间它直接产生并接收值给包含yieldfrom表达式的调用者生成器(“委托生成器”)。此外,当迭代器是生成器时,生成器可以执行return语句返回一个值,该值成为yieldfrom表达式的值。yieldfromexpressions的完整语义可以由生成器协议描述如下:迭代器返回的任何值都直接传递给调用者。使用send()发送给委托生成器的任何值都直接传递给迭代器。如果发送的值为None,则调用迭代器的__next__()方法。如果发送的值不是None,则调用迭代器的send()方法。如果调用引发StopIteration,则恢复委托生成器。任何其他异常都传递给委托生成器。除了GeneratorExit之外,传递给委托生成器的任何异常都将传递给迭代器的throw()方法。如果调用引发StopIteration,则恢复委托生成器。任何其他异常都传递给委托生成器。如果一个GeneratorExit异常被传递给委托生成器,或者如果委托生成器的close()方法被调用,迭代器的close()方法将被调用(如果有的话)。如果调用时发生异常,将传递给委托生成器。否则,在委托生成器中抛出GeneratorExit。yieldfrom表达式的值是迭代器终止时引发的StopIteration异常的第一个参数。在生成器中返回expr会导致在退出生成器时引发StopIteration(expr)。StopIteration的增强为了方便起见,StopIteration异常被赋予一个值属性来保存它的第一个参数,如果没有参数则为None。形式语义本节使用Python3语法。1、RESULT=yieldfromEXPR句子等同于以下句子:_i=iter(EXPR)try:_y=next(_i)exceptStopIterationas_e:_r=_e.valueelse:while1:try:_s=yield_yexceptGeneratorExitas_e:try:_m=_i.close除了AttributeError:passelse:_m()raise_eexceptBaseExceptionas_e:_x=sys.exc_info()try:_m=_i.throwexceptAttributeError:raise_eelse:try:_y=_m(*_x)exceptStopIterationas_e:_r=_e.valuebreakelse:try:if_sisNone:_y=next(_i)else:_y=_i.send(_s)exceptStopIterationas_e:_r=_e.valuebreakRESULT=_r2、在生成器中,returnvalue语句在语义上等同于raiseStopIteration(value),只是当前返回的生成器中的except子句无法捕获此异常3.StopIteration异常的行为定义如下:classStopIteration(Exception):def__init__(self,*args):iflen(args)>0:self.value=args[0]else:self.value=NoneException.__init__(self,*args)Rational重构原则上面提到的大部分语义,基本原理其背后源于重构生成器代码的愿望。即希望将包含一个或多个yield表达式的代码段分离成一个单独的函数(使用常规手段处理范围内的变量引用等),yieldfrom表达式可以调用功能。在合理可行的情况下,此类复合生成器的行为应与原始非分离生成器完全相同,包括调用__next__()、send()、throw()和close()。选择子迭代器(而不是生成器)的语义是对生成器情况的合理概括。提议的语义对重构有以下限制:在完全保留相同行为的情况下,不能分离出捕获GenetatorExit而不重新抛出的代码块。如果StopIteration异常被抛入委托生成器,则分离生成器的行为可能与原始代码的行为不同。由于这些用例几乎不存在,因此不值得为支持它们而付出额外的复杂性。它是如何结束的当在yieldfrom处暂停并使用close()方法显式终止委托生成器时,关于是否也终止子迭代器存在一些争论。反对这一点的一个论点是,如果在其他地方引用了子迭代器,那么这样做会导致过早结束子迭代器。对非引用计数Python实现的考虑导致了它们应该显式终止的结论,因此显式终止子迭代器与所有类型的Python实现上的非重构迭代器具有相同的效果。这里所做的假设是,在大多数用例中,不会共享子迭代器。在子迭代器被共享的极少数情况下,这可以通过阻止调用throw()和close()的装饰器来实现,或者通过使用yieldfrom以外的方法调用子迭代器。GeneratorsasThreads使生成器能够返回值的动机也考虑了使用生成器来实现轻量级线程。当以这种方式使用生成器时,将轻量级线程的计算分散到许多函数上是合理的。人们希望能够像普通函数一样调用子生成器,向其传递参数并接收返回值。使用建议的语法,可以将类似以下y=f(x)的表达式(其中f是普通函数)转换为委托调用y=yieldfromg(x),其中g是生成器。通过将g想象成一个可以被yield语句暂停的普通函数,可以推断出结果代码的行为。当以这种方式将生成器用作线程时,通常对yield传入或传出的值不感兴趣。但是,也有线程可以充当项目的生产者或消费者的示例。yieldfrom表达式允许线程的逻辑根据需要传播到尽可能多的函数,项目的生产和消费发生在任意子函数中,并且这些项目自动路由到/从它们的最终源/目的地。对于throw()和close(),可以合理预期,如果从外部向线程抛出异常,应该先抛到线程挂起的最内层生成器中,然后再从那里传递出去;并且如果通过从外部调用close()来终止线程,它也应该从最内层到外层终止活动生成器链。选择语法提出的具体语法,顾名思义,没有引入任何新的关键字,并清楚地突出了它与普通yield的区别。优化当生成器列表很长时,使用专门的语法为优化提供了可能性。例如,当递归遍历树结构时,可能存在这样的生成器链。在链上传递__next__()调用和yield返回值可能会产生O(n)开销,或者在最坏的情况下为O(n**2)。一种可能的策略是向生成器对象添加一个槽以保存委托给它的生成器。在生成器上调用__next__()或send()时,首先检查插槽,如果非空,则激活它引用的生成器。如果StopIteration被引发,槽被清空并且主生成器被激活。这将减少一系列C函数调用的委托开销,而不涉及Python代码的执行。一个可能的增强是在循环中迭代整个生成器链并直接激活最后一个生成器,尽管StopIteration的处理会更复杂。使用StopIteration返回值有多种方法可以将生成器的返回值传回。还有其他选择,例如将其存储为生成器-迭代器对象的属性,或将其作为子生成器的close()方法调用的值返回。但是,由于以下原因,此PEP中提出的机制很有吸引力:使用通用的StopIteration异常,可以轻松地将其他类型的迭代器添加到协议中,而无需添加额外的属性或close()方法。它简化了实现,因为子生成器的返回值变得可用的点与引发异常的点相同。推迟到任意时间需要将返回值存储在某处。被拒绝的提案一些想法被讨论并被拒绝。建议:应该有一些方法来避免对__next__()的调用,或者将其替换为具有指定值的send()调用,以支持装饰生成器,以便初始__next__()。解决方案:超出本提案的范围。此类生成器不应与yieldfrom一起使用。建议:如果关闭子迭代器引发带有返回值的StopIteration异常,则从close()调用返回该值给委托构建器。这个函数的动机是通过关闭生成器来通知传递给生成器的最后一个值。关闭的生成器捕获GeneratorExit,完成其计算,并返回最终成为close()调用的返回值的结果。解决方案:close()和GeneratorExit的这种用法与当前的纾困和清理机制角色不兼容。这要求当委托生成器在子生成器关闭后关闭时,委托生成器可以恢复,而不是重新引发GeneratorExit。但这是不可接受的,因为调用close()进行清理的目的是不能保证委托生成器将正确终止。向消费者发送最终值信号可以通过其他方式更好地处理,例如发送哨兵值或抛出生产者和消费者都能识别的异常。然后,消费者可以检查哨兵或异常,通过完成其计算并正常返回来做出响应。该方案在委托存在的情况下表现正确。建议:如果close()没有返回任何值,如果StopIteration的值不是None,则抛出异常。解决方案:没有明确的理由这样做。忽略返回值在Python的其他任何地方都不会被认为是错误。批评根据该提案,yieldfrom表达式的值将以与正常yield表达式截然不同的方式生成。这意味着不包含yield表达式的其他语法可能更合适,但到目前为止还没有提出可接受的替代方案。拒绝的备选方案包括call、delegate和gcall。有人建议应该使用子生成器中return以外的一些机制来处理yieldfrom表达式的返回值。但是,这会干扰将子生成器视为可挂起函数的目的,因为它不能像其他函数那样返回值。有人批评使用异常传递返回值是“滥用异常”,没有任何具体理由可以证明。无论如何,这只是一个实施建议;可以使用其他机制而不会丢失本提案的任何关键特征。建议使用与StopIteration不同的异常来返回值,例如GeneratorReturn。但是,没有提出令人信服的实际原因,向StopIteration添加value属性可以减轻从异常(可能存在也可能不存在)中提取返回值的所有困难。此外,使用不同的异常意味着,与普通函数不同,在生成器中没有值的return将不等同于returnNone。可选提案之前已经提到过类似的提案,其中一些语法使用yield而不是yieldfrom。虽然yield更简洁,但它与普通yield看起来太相似了,并且在阅读代码时可能会忽略差异。据作者所知,以前的提案只关注yield值,因此被批评为它们替换的两行for循环不够烦人,不足以证明新语法的合理性。该提案通过处理完整的生成器协议提供了额外的好处。附加材料已经提出了所提议语法的一些用例,并且已经实现了基于上述概述的第一个优化原型。示例和实现问题跟踪器问题11682提供了为Python3.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/pep...----------------(翻译结束)------------相关链接:PEP背景知识:学Python,怎么能不懂PEP呢?PEP翻译计划:https://github.com/chinesehua...[[翻译]PEP255--Simplegenerator](https://mp.weixin.qq.com/s/vj...[[翻译]PEP342--EnhancedGenerators:Coroutines](https://mp.weixin.qq.com/s/M7...[[翻译]PEP525--AsynchronousGenerators](https://mp.weixin.qq.com/s/fy...公众号【Python猫】,专注于Python技术、数据科学和深度学习,力图打造一个有趣好用的学习分享平台。本期连载优质系列文章,有喵星哲学猫系列、Python进阶系列、好书推荐系列、优质英文推荐及译文等,欢迎关注。PS:后台回复“爱学习”,送学习大礼包。