ジェネレータ (yield) | Python-izm

ジェネレータ

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

ジェネレータの基礎

ジェネレータは反復可能なオブジェクトです。forなどでループ処理を行うことができますが、リストなどで反復処理を行う場合と異なる点は、その都度必要な分だけ値を生成して返す点にあります。
※下記コードは概念的なものなので実際に動作させることはできません。

# ループ開始前に 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のように、想定しやすく汎用的なものはすでに用意されています。ジェネレータの活用は慣れていないと難しいと思われますので、こういった仕組みもある、という程度の理解でまずは構わないと思います。

ジェネレータ関数 – yield

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

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

func_sample()
おはよう
こんにちは
こんばんは

ここまでは何ら問題ありません。それではprintyieldへ変更してみましょう。

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

func_sample()

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

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

for i in func_sample():
    print(i)

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

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

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

このようにnext(<ジェネレータ>)<ジェネレータ>.next()で結果を得ることもできます。一回目のnextで最初の「おはよう」が返されます。この時点でfunc_sampleは次の呼び出しがあるまでyield ‘おはよう’の行で待機状態に入ります。さらに次のnextで「こんにちは」が返され、同じように待機状態となり、最後の「こんばんは」も同じです。このようにその都度yieldで値を返すのがジェネレータです。
なおnextを用いて値を取得するのは、for文などが内部で行っている事と同じです。上記コードはすでにすべてのyieldを処理しているので、もう一度nextを呼び出すとStopIterationが発生します。for文などは、この例外を捕捉してループを抜ける処理を行っています。

ジェネレータ式

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

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

print(gen_sample)
for i in gen_sample:
    print(i)
 at 0x000000000259CA68>
おはよう
こんにちは
こんばんは