return
文の確認
yield
文はreturn
文と同様に関数の中で使われ、戻り値を指定するが、その挙動は全く異なる。
まず準備として、通常のreturn
文を持つ関数の動作を確認。呼び出されるたびに常に関数の先頭からreturn
文まで実行される。
1 2 3 4 5 6 7 8 9 10 11 |
def func0(): print("called") return 1 print(func0()) print(func0()) # called # 1 # called # 1 |
yield
文にするとジェネレーターが生成される
このreturn
文をyield
文に変更してみると、関数の戻り値を返すのではなく、この関数がジェネレーターオブジェクトのコンストラクターとなっている。
1 2 3 4 5 6 7 |
def func1(): print("called") yield 2 print(func1()) # <generator object func1 at 0x047BCF30> |
ジェネレーターオブジェクトには、(Python3では)__next__()
メソッドがあって、ジェネレーターで生成された値を順次取り出してくれる。そこで上のfunc1()でジェネレーターのインスタンスを生成し、直接値を取り出してみる。
1 2 3 4 5 6 7 8 9 |
generator = func1() print(generator.__next__()) print(generator.__next__()) # called # 2 # Traceback (most recent call last): # ..... # StopIteration |
このジェネレーターは値を1つしか生成しないので、2つ目を取り出そうとするとStopIteration
例外を投げる。
yield
文によるジェネレーターの挙動
以下の例は、3つの値を返すジェネレーターの例で、確認のためにyield
文の前の処理をprint
文で表示させるようにしている。なお、ここでは__next__()
メソッドの代わりに組み込み関数next()
を用いている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
def func3(): print("He said:") yield "How are you?" print("She replied and asked:") yield "I'm fine. How are you?" print("He replied:") yield "I'm fine too." generator = func3() print(next(generator)) print(next(generator)) print(next(generator)) # He said: # How are you? # She replied and asked: # I'm fine. How are you? # He replied: # I'm fine too. |
関数で生成されるのはジェネレーターでStopIteration
を投げるので、次のようにfor
文で使える。
1 2 3 4 5 6 7 8 9 |
for x in func3(): print(x) # He said: # How are you? # She replied and asked: # I'm fine. How are you? # He replied: # I'm fine too. |
yiled
文とreturn
文を混ぜた場合
関数の中でyield
文を書くと、return
文があってもジェネレーターが生成される。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def func4(): yield 1 yield 2 yield 3 return 4 print(func4()) for x in func4(): print(x) # <generator object func4 at 0x03B5CF30> # 1 # 2 # 3 |
ただしreturn
文があるとそこでStopIteration
が投げられる。このとき、return
文で指定した戻り値が得られるようだが、ジェネレーターとしては無視されるらしい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def func5(): yield 1 yield 2 return 3 yield 4 g = func5() print(next(g)) print(next(g)) print(next(g)) # 1 # 2 # Traceback (most recent call last): # ..... # StopIteration: 3 |
このジェネレーターをfor
文で使うと、以下のようにreturn
文の手前まで実行される。
1 2 3 4 5 |
for x in func5(): print(x) # 1 # 2 |
実装の例
たとえば引数を与えて、その数以下であるフィボナッチ数列を返すジェネレーターを考える。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
ef fibonacci(max): a = 1 b = 1 yield a yield b next_value = a + b while next_value < max: yield next_value a = b b = next_value next_value = a + b for x in fibonacci(100): print(x) # 1 # 1 # 2 # 3 # 5 # 8 # 13 # 21 # 34 # 55 # 89 |