python関数におけるyieldの使い方の詳細

3709 ワード

まずreturnとyieldの違いを比較します.
return:プログラム関数に値を返します.返した後、関数は実行されず、完全に終了します.
yield:yield付き関数は反復器で、関数が値を返すと、ある位置にとどまり、関数値を返すと、プログラムが終了するまで前の位置で実行されます.
まず、もしあなたがまだyieldに対して初歩的な認識を持っていないならば、あなたはまずyieldを“return”と見なして、これは直観的で、それはまずreturnで、普通のreturnはどういう意味で、プログラムの中である値を返して、帰ってからプログラムはもう下へ実行しません.returnを見てからジェネレータ(generator)の一部(yield付きの関数こそ本当の反復器)と見なします.よし、これらが分からない場合は、yieldをreturnと見なして、次のプログラムを直接見ると、yieldのすべての意味がわかります.
  def foo():
        print("starting...")
        while True:
            res = yield 4
            print("res:",res)
    g = foo()
    print(next(g))
    print("*"*20)
    print(next(g))

このように簡単な数行のコードでyieldとは何かを理解させ、コードの出力はこれです.
 starting...
    4
    ********************
    res: None
    4

コードの実行順序を直接説明します.コードの単一ステップデバッグに相当します.
1.プログラムが実行を開始すると、foo関数にyieldキーワードがあるため、foo関数は実際に実行されるのではなく、まずジェネレータg(オブジェクトに相当)が得られる
2.nextメソッドが呼び出されるまでfoo関数は正式に実行を開始し、foo関数のprintメソッドを実行してからwhileループに入ります.
3.プログラムはyieldキーワードに出会って、それからyieldをreturnと考えて、returnは1つの4の後で、プログラムは停止して、res操作に値を割り当てることを実行していないで、この時next(g)文の実行は完成して、だから出力する前の2行(1つ目はwhileの上のprintの結果で、2つ目はreturnの出た結果)はprint(next(g))を実行する結果で、
4.プログラム実行print(「*」*20)、出力20個*
5.また次のprint(next(g))を実行し始めます.このときは上とあまり差がありませんが、このときはさっきのnextプログラムが停止したところから実行されます.つまりresの付与操作を実行するので、注意してください.このとき賦値操作の右側には値がない(さっきはreturnが出ていたので、賦値操作の左にパラメータが渡されていない)ので、このときres賦値はNoneなので、次の出力はres:Noneです.
6.プログラムはwhileで実行され続け、yieldに再び遭遇します.このときもreturnが4を出し、プログラムが停止します.print関数が出力する4が今回のreturnが4を出ます.
ここまで来るとyieldとreturnの関係と違いがわかるかもしれませんが、yield付きの関数は関数ではなくジェネレータです.このジェネレータにはnext関数があります.nextは「次へ」どの数を生成するかに相当します.今回のnextが始まる場所は前回のnextが停止した場所で実行されるので、nextを呼び出すとき、ジェネレータはfoo関数の開始から実行されませんが、前のステップで停止した場所から開始し、yieldに遭遇するとreturnが生成する数を出して、このステップは終了します.
****************************************************************************************************************************************
 def foo():
        print("starting...")
        while True:
            res = yield 4
            print("res:",res)
    g = foo()
    print(next(g))
    print("*"*20)
    print(g.send(7))

このジェネレータのsend関数の例を見て、この例は上の例の最後の行を交換して、結果を出力します.
 starting...
    4
    ********************
    res: 7
    4

まずsend関数の概念を大まかに説明します:この時あなたは上のあの紫色の字に気づくべきで、それから上のあのresの値はどうしてNoneで、これは7になって、いったいどうして、これはsendが1つのパラメータをresに送ったので、上で述べたように、returnの時、4をresに与えていないので、次回実行する時は賦値操作を続けるしかありません.仕方なくNoneとして付与し、sendを使用すると、実行を開始するときは、前回(return 4以降)に続いて実行し、まず7をresに付与し、nextの役割を実行し、次回のyieldに出会ってreturnが結果を出して終了する.
5.プログラムはg.send(7)を実行し、yieldキーワードの行から下へ実行し続け、sendは7という値をres変数に付与する
6.sendメソッドにnext()メソッドが含まれているため、プログラムはprintメソッドを実行し続け、whileループに再び進みます.
7.プログラム実行は再びyieldキーワードに遭遇し、yieldは後の値を返した後、nextメソッドまたはsendメソッドが再び呼び出されるまでプログラムは再び一時停止する.
 
これで終わりですが、なぜこのジェネレータを使うのかというと、リストを使うと、0,1,2,3,4,5,6を取るなど、より大きなスペースを占めるからです.1000
あなたはそうするかもしれません.
   for n in range(1000):
        a=n

このときrange(1000)はデフォルトで1000個の数を含むlistを生成するので、メモリを占めています.
このとき先ほどのyieldをジェネレータに組み合わせて実装したり、xrange(1000)というジェネレータで実装したりすることができます
yieldの組み合わせ:
    def foo(num):
        print("starting...")
        while num<10:
            num=num+1
            yield num
    for n in foo(0):
        print(n)

出力:
    starting...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

 xrange(1000):
   for n in xrange(1000):
        a=n

ここで注意したいのはpython 3にはxrange()がありません.python 3ではrange()がxrange()です.python 3でrange()のタイプを見ることができます.リストではなく、python 3でrange()のタイプを見ることができます.結局、これは最適化する必要があります.