Pythonのyield(ジェネレータ編)

はじめに

なんとなくで使ってたので、Pythonのyieldについてちょっと調べてみました。
この記事の内容は Python 3.5.2/Windows10 で試してます。

yieldとは

Python本家のドキュメントによると

yield 式は ジェネレータ 関数の関数定義内でのみ使用されます。関数本体中で yield 式 を使用すると、その関数はジェネレータになります。

ジェネレータ関数が呼び出された時、ジェネレータとしてのイテレータを返します。 …

上記の引用で出てきたジェネレータという用語については、以下のドキュメントによると

A function which returns a generator iterator. It looks like a normal function except that it contains yield expressions for producing a series of values usable in a for-loop or that can be retrieved one at a time with the next() function.

だそうです。うーん。。いまいちピンとこないですが…

関数内でyieldがある関数はジェネレータ関数になり、その関数を呼ぶとイテレータを返すということです。

とりあえず、一旦動かしてみます。

yieldのありなしで関数を動かして比較してみる

早速yieldありの関数を比較してみます。

yieldなしの関数

関数定義はこんな感じで

>>> def normal_func():
...	print("-> start")
...	return 1

実行結果は当然ですが、こんな感じです。

>>> f = normal_func()
-> start
>>> type(f)
<class 'int'>

ここでのポイントは以下の2点です。

  • 関数がすぐ実行され、 -> start が標準出力にでている
  • 関数の戻り値はreturn 1 の int 型

yieldありの関数

さっき関数のreturnをyieldに変えてます。

>>> def yield_func():
...	print("-> start")
...	yield 1

実行結果は、こんな感じです。

>>> f = yield_func()
>>> type(f)
<class 'generator'>

ここでのポイントは以下の2点です。

  • 関数呼び出しの時点では実行されてないので、start が標準出力にでていない
  • 関数の戻り値はint型ではなく、generator型

↑で関数を代入した変数aについて型チェックしてみると

>>> import collections
>>> isinstance(f, collections.Iterator)
True

ということなので、yieldを含む関数は確かにドキュメント通り、ジェネレータとしてのイテレータを返しているようです。

ついでにinspectモジュールでも見てみると、こちらもドキュメント通りのレスポンスです。

>>> import inspect
>>> inspect.isgenerator(f)
True
>>> inspect.isgeneratorfunction(yield_func)
True

ふむふむ。
もう少し実践的なサンプルで詳しくみていきます。

yieldを使った実践的な例

以下のようなフィボナッチ数を無限に返す関数を書くことができます。

>>> def fib():
...	print("-> fib start")
...	a, b = 0, 1
...	while True:
...	    print("-> before yield. a=" + str(a) + ", b=" + str(b))
...	    yield a
...	    print("-> after yield.)
...	    a, b = b, a + b

実際に動かして確認してみます。

>>> it = fib()
>>> next(it)
-> fib start.
-> before yield. a=0, b=1
0
>>> next(it)
-> after yield.
-> before yield. a=1, b=1
1
>>> next(it)
-> after yield.
-> before yield. a=1, b=2
1
>>> next(it)
-> after yield.
-> before yield. a=2, b=3
2

ここでのポイントは以下の3点です。

  • yieldで関数が一時停止し、値を返す
  • nextでyieldで一時停止したところから関数を再開
  • 再開した関数はyieldで一時停止した際のプライベート名前空間やローカル変数を引き継いでいる

 

確認できたことのまとめ

動かしてみて確認できたことをまとめるとyieldは関数に以下の機能をあたえるキーワードということがわかりました。

  • 関数をイテレータ化(ジェネレータ化)する
  • 関数を一時停止/再開可能にする

またこれ以外にもyield from という構文やコルーチンといった話もyieldに絡む話なので、そのあたりについては別の記事で検証していきたいと思います。

参考リンク

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA