Python入門から応用までの学習サイト


ジェネレータ (yield)

Pythonにはジェネレータという仕組みがあります。ジェネレータ関数とジェネレータ式があり、単に「ジェネレータ」という場合はジェネレータ関数の方を指します。またジェネレータ関数ではyieldというキーワードが重要になります。

ジェネレータは反復可能なオブジェクトです。forなどでループ処理を行うことができますが、リストなどで反復処理を行う場合と異なる点は、その都度、必要な分だけ値を生成して返す点にあります。

※下記コードは概念的なものなので実際に動作させることはできません。

# -*- coding: utf-8 -*- 


# ループ開始前に 1 から 10 まですべてを確保
for i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
    print i

# 呼び出されるごとに値を生成
for i in <ジェネレータ>:
    print i

ジェネレータを用いる利点はメモリを効率的に使用できることです。たとえば上記コードでは1から10ですが、1から100000000までではどうでしょうか。こういった場合は最初からすべてを確保すると大量のメモリを消費します。そのため「その都度」「必要な分だけ」値を生成し、それを返すジェネレータが有効な手段となります。

※Python 2系でこの件を解決する場合は xrange を用いると良いでしょう。 xrangeは厳密にいえばジェネレータではありませんが、それに近い概念で動作します。


しかしながら巨大なデータを扱ったり、フレームワークを作成したりすることを除いて、さほど利用する機会は多くありません。またxrangeのように、想定しやすく汎用的なものはすでに用意されています。ジェネレータの活用は慣れていないと難しいと思われますので、こういった仕組みもある、という程度の理解でまずは構わないと思います。



ジェネレータ(関数)を作成する場合はどうすれば良いかを見てみましょう。まずは普通の関数を作成します。

# -*- coding: utf-8 -*- 


def func_sample():
    print u'おはよう'
    print u'こんにちは'
    print u'こんばんは'

func_sample()

--実行結果--

おはよう
こんにちは
こんばんは

ここまでは何ら問題ありません。それでは「print」を「yield」へ変更してみましょう。

# -*- coding: utf-8 -*- 


def func_sample():
    yield u'おはよう'
    yield u'こんにちは'
    yield u'こんばんは'

func_sample()

printではなくなったので当然といえば当然ですが、「func_sample」を呼び出しても何も出力されなくなりました。なおこの時点ですでに「func_sample」はジェネレータ(関数)です。次のように値を取得することができます。

# -*- coding: utf-8 -*- 


def func_sample():
    yield u'おはよう'
    yield u'こんにちは'
    yield u'こんばんは'

for i in func_sample():
    print i

先ほどと同じような出力が得られたはずです。Pythonでは関数内に「yield」が含まれると、その関数はジェネレータとなり反復可能なオブジェクトとなります。そのためforでループ処理を行うことができるようになりました。またジェネレータは次のように値を取り出すこともできます。

# -*- coding: utf-8 -*- 


def func_sample():
    yield u'おはよう'
    yield u'こんにちは'
    yield u'こんばんは'

f = func_sample()
print next(f)
print next(f)
print f.next()

このように「next(<ジェネレータ>)」や「<ジェネレータ>.next()」で結果を得ることもできます。一回目の「next」で最初の「おはよう」が返されます。この時点で「func_sample」は次の呼び出しがあるまで「yield u'おはよう'」の行で待機状態に入ります。さらに次の「next」で「こんにちは」が返され、同じように待機状態となり、最後の「こんばんは」も同じです。このようにその都度yieldで値を返すのがジェネレータです。


なお「next」を用いて値を取得するのは、for文などが内部で行っている事と同じです。上記コードはすでにすべてのyieldを処理しているので、もう一度「next」を呼び出すと「StopIteration」が発生します。for文などは、この例外を捕捉してループを抜ける処理を行っています。



ジェネレータ式は簡易的にジェネレータを作成する手段です。内包表記 のような形で記述することができます。

# -*- coding: utf-8 -*- 


gen_sample = (i for i in u'おはよう こんにちは こんばんは'.split())

print gen_sample
for i in gen_sample:
    print i

--実行結果--

<generator object <genexpr> at 0x000000000259CA68>
おはよう
こんにちは
こんばんは



Python
スタートブック


入門 Python 3


Effective
Python


退屈なことは
Pythonにやらせよう

 
 
 

次はファイルの読み書きを学びましょう!



確かな力が身につく
Python「超」入門




P  R