Pythonの反復処理などでとても便利な機能があります。通常、for文の反復処理のコードを実行すると、一度に繰り返し処理を行います。
ここで、呼び出しごとに繰り返し処理を行い、その終了時の状態を覚えておいて次の呼び出しで続きの繰り返し処理を行うようなことができると都合がいいことがあります。これを可能にするのが、ジェネレータです。
例えば、繰り返し処理に巨大なシーケンスを用意するのは面倒ですよね。例えば、1から1000000までの整数のリストを使って繰り返し処理なんて作るのはとても不合理です。こんなときはおそらくrange(1, 1000000)などを利用しますが、これもジェネレータの一つです。
このように大きくなりそうなシーケンスを処理するのにジェネレータを使って反復処理したりするのは、メモリ消費上でもとても有効になります。
ここではジェネレータがどんなものか、具体的に見て行きましょう。ここもPythonの入門としては、少し難しい内容になるかなとは思います。
for文の確認
ジェネレータは繰り返し処理にシーケンスを作るオブジェクトですが、ここで一度、Pythonのfor文を確認しておいて、あとでジェネレータとの違いを見ていこうと思います。
for文を学習した時のことを思い出してみましょう。
例として出したコードはこれでした。
gundam_list = ["EXIA","DYNAMES","KYRIOS","VIRTUE"]
for i in gundam_list:
print(i)
実行するとこうなるのは、わかりますね。
順に最初のリストの値がfor-in文で取り出されて、一度に繰り返し出力されています。
これをジェネレータとして扱うとどうなるのか見ていきましょう。
ジェネレータ関数を作る
上のコードを書き換えて、ジェネレータ関数を作ります。上で使ったコードは、こんな関数にすることができます。
def gundam_list():
print("EXIA")
print("DYNAMES")
print("KYRIOS")
print("VIRTUE")
gundam_list()
簡単なコードなので呼び出すと同じ結果になるのはわかりますね。
このコードをジェネレータにするには、値をyield文で返すようにします。yield文を使えばジェネレータという感覚でPythonは考えたらいいでしょう。厳密にはそうとも言えない面もあるようですが、ここではそう理解しておきます。
では、このコードをyield文で書き換えてみましょう。
def gundam_list():
yield "EXIA"
yield "DYNAMES"
yield "KYRIOS"
yield "VIRTUE"
これを出力するには、呼び出した関数をオブジェクトとして引数に使って、順に取り出すことで出力します。for文で取り出してみましょう。
def gundam_list():
yield "EXIA"
yield "DYNAMES"
yield "KYRIOS"
yield "VIRTUE"
for i in gundam_list():
print(i)
このコードをAtomで実行してみましょう。ファイル名はgenerator.pyにしています。
結果はこうなります。
これまでと同じ結果が出力されています。
これを違う出力の仕方をしてみます。イテレータオブジェクトを取り出すnext()関数を使って次のようなコードを書いてみます。
def gundam_list():
yield "EXIA"
yield "DYNAMES"
yield "KYRIOS"
yield "VIRTUE"
g = gundam_list()
print(next(g))
next()関数で呼び出された内容をprint()で出力されるのはわかると思いますが、結果はこうなります。
要素の一つしか出力されていません。
next()を2回続けて呼び出してみましょう。
def gundam_list():
yield "EXIA"
yield "DYNAMES"
yield "KYRIOS"
yield "VIRTUE"
g = gundam_list()
print(next(g))
print(next(g))
今度はこうなります。
2つ目まで呼び出されています。
4つ呼び出してみましょう。
4つ出力されているのがわかります。このように、ジェネレータを使うと一つづつ呼び出すことができるわけです。
ちなみに5つ呼び出してみましょう。
StopIterarionということで処理が止まっています。
ジェネレータオブジェクトの面白いところは、一つづつ処理を行えることと処理の間に別のことを行うことができるということです。
次のようなパターンで呼び出すコードに書き換えてみます。
def gundam_list():
yield "EXIA"
yield "DYNAMES"
yield "KYRIOS"
yield "VIRTUE"
g = gundam_list()
print(next(g))
print("1機出撃しました!")
print(next(g))
print("2機目も出撃しました!")
print(next(g))
print(next(g))
next()関数で呼び出す間に、別の文字列を表示する処理も入れてみました。
実行するとこうなります。
このように、ジェネレータは一気に取り出すこともできますし、一つずつ取り出すこもでき、また、間に別の処理を行ったあとで続けて取り出すということができます。
for文で一気に反復処理するものとは違う操作ができるということがわかると思います。
ジェネレータは、処理の終了時の状態を覚えておいて次の呼び出しで続きの繰り返し処理を行うようなことができるということがこれらの例からわかると思います。
まとめ
Pythonのジェネレータは、反復処理の途中での終了時の状態を覚えておいて、次の呼び出しで続きの繰り返し処理を行うようなことができる便利な仕組みです。
これは、巨大なリストなどのイテレータを用意しなくてもよくなったり、処理を途中で止めて別の処理を行うなどできると、メモリ消費の上でも効率的になります。
Pythonのジェネレータ関数は、yieldで値を返す形で定義され、next()関数で一つずつ取り出すことができます。
Python初心者にはちょっと難しい内容になるかもしれません。