Python 3の構文からのyieldの詳細な説明

高洛峰
リリース: 2017-02-21 10:11:00
オリジナル
1311 人が閲覧しました

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 fromyield我知道,但是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——那么:

  1. b迭代产生的每个值都直接传递给a的调用者。

  2. 所有通过send方法发送到a的值都被直接传递给b. 如果发送的 值是None,则调用b的__next__()方法,否则调用b的send 方法。如果对b的方法调用产生StopIteration异常,a会继续 执行yield from后面的语句,而其他异常则会传播到a中,导 致a在执行yield from的时候抛出异常。

  3. 如果有除GeneratorExit以外的异常被throw到a中的话,该异常 会被直接throw到b中。如果b的throw方法抛出StopIteration, a会继续执行;其他异常则会导致a也抛出异常。

  4. 如果一个GeneratorExit异常被throw到a中,或者a的close 方法被调用了,并且b也有close方法的话,b的close方法也 会被调用。如果b的这个方法抛出了异常,则会导致a也抛出异常。 反之,如果b成功close掉了,a也会抛出异常,但是是特定的 GeneratorExit异常。

  5. a中yield from表达式的求值结果是b迭代结束时抛出的 StopIteration异常的第一个参数。

  6. 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()

🎜PEP-380🎜🎜🎜 さて、このタイトルは Google を通じて思いつきました。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 - の場合: 🎜
  1. 🎜 2 回の反復によって生成されたすべての値は、呼び出し元に直接渡されます。 🎜
  2. 🎜send メソッドを通じて a に送信されたすべての値は、直接 b に渡されます。送信された値が None の場合、b の __next__() メソッドが呼び出されます。それ以外の場合は、b の send メソッドと呼ばれます。 b へのメソッド呼び出しによって StopIteration 例外が生成された場合、a は yield from に続くステートメントの実行を継続し、他の例外が a に伝播され、 a が yield from を実行します。 > 例外をスローします。 🎜
  3. 🎜 GeneratorExit 以外の例外が a にスローされた場合、その例外は直接 b にスローされます。 b の throw メソッドが StopIteration をスローした場合、 a は他の例外の実行を続行し、 a も例外をスローします。 🎜
  4. 🎜 GeneratorExit 例外が a にスローされた場合、または a の close メソッドが呼び出され、b にも close メソッドがある場合、b の close メソッドも呼び出されます。 b のこのメソッドが例外をスローすると、a も例外をスローします。 逆に、b が正常に閉じられた場合、a も例外をスローしますが、これは特定の GeneratorExit 例外です。 🎜
  5. 🎜 a の yield from 式の評価結果は、b の反復の最後にスローされる StopIteration 例外の最初のパラメーターです。 🎜
  6. 🎜b の return <expr> ステートメントは、実際には StopIteration(<expr>) 例外をスローするため、b で return します。の値は、a の yield from 式の戻り値になります。
    🎜
🎜なぜシェンマにはこれほど多くの要件があるのですか? throw メソッドの追加後、特に複数のジェネレーターが一緒にある場合、ジェネレーターの動作は非常に複雑になるため、ジェネレーターを操作するにはプロセス管理と同様のプリミティブが必要になります。上記の要件はすべて、ジェネレーターの本質的に複雑な動作を統一するためのものであるため、当然のことながら単純化することはできません。 🎜🎜PEP の作成者が何を言いたかったのか理解できなかったことを認めます。そのため、それを「リファクタリング」すると役に立つかもしれません。 🎜🎜🎜役に立たない例🎜🎜🎜実際にはこのようなプログラムを書きたくないはずなので、役に立たないですが... とにかく、問題を説明するには十分です。 🎜🎜そのようなジェネレーター関数があると想像してください: 🎜

🎜

def outer2():
 print("Before inner(), I do this.")
 yield from inner()
 print("After inner(), I do that.")
ログイン後にコピー
ログイン後にコピー
🎜🎜🎜この関数によって生成されたジェネレーターは、send メソッドから受け取った値をローカル変数 total に蓄積し、BreakOut 例外が発生したときに他の SwitchSign 例外については、簡単に理解できるはずなので、ここではネタバレしません。 🎜🎜 コードの観点から見ると、inner() 関数によって取得されたジェネレーターは、send を通じて計算用のデータを受け取り、同時に throw メソッドを通じて外部コードの制御を受け入れて別のコードを実行します。これまでのところ、コードの分岐はすべて非常に明確です。 🎜🎜次に、要件の変更により、inner() コードの前後に初期化コードとサイト クリーンアップ コードを追加する必要があります。 「壊れていないコードは触るな」と思うので、 inner() はそのままにして、別の outer() を書いて追加したものを入れることにしました。 outer() のコードであり、inner() と同じ操作インターフェイスを提供します。 inner() はジェネレーターのいくつかの機能を利用するため、outer() も次の 5 つのことを実行する必要があります: 🎜
  1. outer()必须生成一个generator;

  2. 在每一步的迭代中,outer()要帮助inner()返回迭代值;

  3. 在每一步的迭代中,outer()要帮助inner()接收外部发送的数据;

  4. 在每一步的迭代中,outer()要处理inner()接收和抛出所有异常;

  5. 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中文网!

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!