Python 3の構文からのyieldの詳細な説明
Python 3.3 では、ジェネレーターに新しい構文が追加されました。 yield from の機能は何ですか?構文とは何ですか?以下の記事では主にPython 3の構文からのyieldに関する情報を詳しく紹介していますので、必要な方はぜひ参考にしてみてください。
前書き
最近 Autobahn をいじっていて、asyncio に基づいた例が示されていました。それを pypy3 に入れて実行しようと考えましたが、失敗しました。 pip install asyncio
は無効な構文を直接報告しました。一見、2to3 処理に問題があるのではないかと思いました。まあ、多くのパッケージは 2 で書かれ、その後 3 に変換されました。 asyncio は元々バージョン 3.3 以降のみをサポートしていたことが判明したので、コードを振り返ってみると、突然 yield from
という文を見つけました。知っているのですが、yield from
魔法の馬ですか? pip install asyncio
直接报invalid syntax,粗看还以为2to3处理的时 候有问题——这不能怪我,好~多package都是用2写了然后转成3的——结果发 现asyncio本来就只支持3.3+的版本,才又回头看代码,赫然发现一句 yield from
;yield
我知道,但是yield from
是神马?
PEP-380
好吧这个标题是我google出来的,yield from
的前世今生都在 这个PEP里面,总之大意是原本的yield
语句只能将CPU控制权 还给直接调用者,当你想要将一个generator或者coroutine里带有 yield语句的逻辑重构到另一个generator(原文是subgenerator) 里的时候,会非常麻烦,因为外面的generator要负责为里面的 generator做消息传递;所以某人有个想法是让python把消息传递 封装起来,使其对程序猿透明,于是就有了yield from
。
PEP-380规定了yield from
的语义,或者说嵌套的generator应该 有的行为模式。
假设A函数中有这样一个语句
yield from B()
B()
返回的是一个可迭代(iterable)的对象b,那么A()会返回一个 generator——照我们的命名规范,名字叫a——那么:
b迭代产生的每个值都直接传递给a的调用者。
所有通过send方法发送到a的值都被直接传递给b. 如果发送的 值是None,则调用b的
__next__()
方法,否则调用b的send 方法。如果对b的方法调用产生StopIteration异常,a会继续 执行yield from
后面的语句,而其他异常则会传播到a中,导 致a在执行yield from
的时候抛出异常。如果有除GeneratorExit以外的异常被throw到a中的话,该异常 会被直接throw到b中。如果b的throw方法抛出StopIteration, a会继续执行;其他异常则会导致a也抛出异常。
如果一个GeneratorExit异常被throw到a中,或者a的close 方法被调用了,并且b也有close方法的话,b的close方法也 会被调用。如果b的这个方法抛出了异常,则会导致a也抛出异常。 反之,如果b成功close掉了,a也会抛出异常,但是是特定的 GeneratorExit异常。
a中
yield from
表达式的求值结果是b迭代结束时抛出的 StopIteration异常的第一个参数。b中的
return <expr>
语句实际上会抛出StopIteration(<expr>)
异常,所以b中return的值会成为a中yield from
表达式的返回值。
为神马会有这么多要求?因为generator这种东西的行为在加入throw 方法之后变得非常复杂,特别是几个generator在一起的情况,需要 类似进程管理的元语对其进行操作。上面的所有要求都是为了统一 generator原本就复杂的行为,自然简单不下来啦。
我承认我一下没看明白PEP的作者到底想说什么,于是动手“重构” 一遍大概会有点帮助。
一个没用的例子
说没用是因为你大概不会真的想把程序写成这样,但是……反正能说明 问题就够了。
设想有这样一个generator函数:
def inner(): coef = 1 total = 0 while True: try: input_val = yield total total = total + coef * input_val except SwitchSign: coef = -(coef) except BreakOut: return total
这个函数生成的generator将从send方法接收到的值累加到局部 变量total中,并且在收到BreakOut异常时停止迭代;至于另外 一个SwitchSign异常应该不难理解,这里就不剧透了。
从代码上看,由inner()
函数得到的generator通过send接收用于 运算的数据,同时通过throw方法接受外部代码的控制以执行不同 的代码分支,目前为止都很清晰。
接下来因为需求有变动,我们需要在inner()
这段代码的前后分别加 入初始化和清理现场的代码。鉴于我认为“没坏的代码就不要动”,我 决定让inner()
维持现状,然后再写一个outer()
,把添加的代码放在 outer()
里,并提供与inner()
一样的操作接口。由于inner()
利用了 generator的若干特性,所以outer()
yield from
の過去と現在のすべてがこの PEP に含まれています。は元の yield
です。このステートメントは、直接呼び出し元にのみ CPU 制御を返すことができます。ジェネレーターまたはコルーチン内の yield ステートメントを使用してロジックを別のジェネレーター (元のサブジェネレーター) に再構築する場合は、次のようになります。外部のジェネレーターは内部のジェネレーターに対するメッセージ受け渡しを担当するため、非常に面倒です。そのため、誰かがメッセージ受け渡しをプログラマに透過的にするために Python にカプセル化させるというアイデアを思いついたので、yield from
が発生しました。 🎜🎜PEP-380 は、yield from
のセマンティクス、またはネストされたジェネレーターが持つべき動作パターンを指定します。 🎜🎜関数 A にそのようなステートメントがあるとします🎜🎜
def outer1(): print("Before inner(), I do this.") i_gen = inner() input_val = None ret_val = i_gen.send(input_val) while True: try: input_val = yield ret_val ret_val = i_gen.send(input_val) except StopIteration: break except Exception as err: try: ret_val = i_gen.throw(err) except StopIteration: break print("After inner(), I do that.")
B()
は反復可能 (iterable) オブジェクト b を返し、その後 A( ) が返されますジェネレーター - 命名規則によれば、名前は a - の場合: 🎜- 🎜 2 回の反復によって生成されたすべての値は、呼び出し元に直接渡されます。 🎜
- 🎜send メソッドを通じて a に送信されたすべての値は、直接 b に渡されます。送信された値が None の場合、b の
__next__()
メソッドが呼び出されます。それ以外の場合は、b の send メソッドと呼ばれます。 b へのメソッド呼び出しによって StopIteration 例外が生成された場合、a はyield from
に続くステートメントの実行を継続し、他の例外が a に伝播され、 a がyield from
を実行します。 > 例外をスローします。 🎜 - 🎜 GeneratorExit 以外の例外が a にスローされた場合、その例外は直接 b にスローされます。 b の throw メソッドが StopIteration をスローした場合、 a は他の例外の実行を続行し、 a も例外をスローします。 🎜
- 🎜 GeneratorExit 例外が a にスローされた場合、または a の close メソッドが呼び出され、b にも close メソッドがある場合、b の close メソッドも呼び出されます。 b のこのメソッドが例外をスローすると、a も例外をスローします。 逆に、b が正常に閉じられた場合、a も例外をスローしますが、これは特定の GeneratorExit 例外です。 🎜
- 🎜 a の
yield from
式の評価結果は、b の反復の最後にスローされる StopIteration 例外の最初のパラメーターです。 🎜 - 🎜b の
return <expr>
ステートメントは、実際にはStopIteration(<expr>)
例外をスローするため、b で return します。の値は、a のyield from
式の戻り値になります。
🎜
🎜
def outer2(): print("Before inner(), I do this.") yield from inner() print("After inner(), I do that.")
inner()
関数によって取得されたジェネレーターは、send を通じて計算用のデータを受け取り、同時に throw メソッドを通じて外部コードの制御を受け入れて別のコードを実行します。これまでのところ、コードの分岐はすべて非常に明確です。 🎜🎜次に、要件の変更により、inner()
コードの前後に初期化コードとサイト クリーンアップ コードを追加する必要があります。 「壊れていないコードは触るな」と思うので、 inner()
はそのままにして、別の outer()
を書いて追加したものを入れることにしました。 outer()
のコードであり、inner()
と同じ操作インターフェイスを提供します。 inner()
はジェネレーターのいくつかの機能を利用するため、outer()
も次の 5 つのことを実行する必要があります: 🎜outer()
必须生成一个generator;在每一步的迭代中,
outer()
要帮助inner()
返回迭代值;在每一步的迭代中,
outer()
要帮助inner()
接收外部发送的数据;在每一步的迭代中,
outer()
要处理inner()
接收和抛出所有异常;在
outer()
被close的时候,inner()
也要被正确地close掉。
根据上面的要求,在只有yield的世界里,outer()
可能是长这样的:
def outer1(): print("Before inner(), I do this.") i_gen = inner() input_val = None ret_val = i_gen.send(input_val) while True: try: input_val = yield ret_val ret_val = i_gen.send(input_val) except StopIteration: break except Exception as err: try: ret_val = i_gen.throw(err) except StopIteration: break print("After inner(), I do that.")
WTF,这段代码比inner()
本身还要长,而且还没处理close操作。
现在我们来试试外星科技:
def outer2(): print("Before inner(), I do this.") yield from inner() print("After inner(), I do that.")
除了完全符合上面的要求外,这四行代码打印出来的时候还能省点纸。
我们可以在outer1()
和outer2()
上分别测试 数据 以及 异常 的传递,不难发现这两个generator的行为基本上是一致的。既然如此, 外星科技当然在大多数情况下是首选。
对generator和coroutine的疑问
从以前接触到Python下的coroutine就觉得它怪怪的,我能看清它们的 行为模式,但是并不明白为什么要使用这种模式,generator和 coroutine具有一样的对外接口,是generator造就了coroutine呢,还 是coroutine造就了generator?最让我百思不得其解的是,Python下 的coroutine将“消息传递”和“调度”这两种操作绑在一个yield 上——即便有了yield from
,这个状况还是没变过——我看不出这样做 的必要性。如果一开始就从语法层面将这两种语义分开,并且为 generator和coroutine分别设计一套接口,coroutine的概念大概也会 容易理解一些。
更多Python 3中的yield from语法详解相关文章请关注PHP中文网!

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック











LinuxターミナルでPythonバージョンを表示する際の許可の問題の解決策PythonターミナルでPythonバージョンを表示しようとするとき、Pythonを入力してください...

fiddlereveryversings for the-middleの測定値を使用するときに検出されないようにする方法

10時間以内にコンピューター初心者プログラミングの基本を教える方法は?コンピューター初心者にプログラミングの知識を教えるのに10時間しかない場合、何を教えることを選びますか...

PythonのPandasライブラリを使用する場合、異なる構造を持つ2つのデータフレーム間で列全体をコピーする方法は一般的な問題です。 2つのデータがあるとします...

UvicornはどのようにしてHTTPリクエストを継続的に聞きますか? Uvicornは、ASGIに基づく軽量のWebサーバーです。そのコア機能の1つは、HTTPリクエストを聞いて続行することです...

Investing.comの反クラウリング戦略を理解する多くの人々は、Investing.com(https://cn.investing.com/news/latest-news)からのニュースデータをクロールしようとします。
