【Python】ジェネレータとは?|サックリかんたん解説

初心者向けサックリかんたん解説

ここでは一通りPythonを勉強した私が「ここはわかりづらいだろうな」と思った点や、最近Pythonの勉強をし始めたという初心者の方がつまづきがちな点をピックアップし、長ったらしくならないようできるだけ簡単にサクッと解説していきます。

ジェネレータ (generator) とは

まずは、ジェネレータの厳密な定義を見てみましょう。

厳密な定義

ジェネレータはPythonのシーケンスを作成するオブジェクトで、これによってシーケンス全体を作ってメモリに格納しなくても巨大になることがあるようなシーケンスを反復処理できる。

これを見て理解できた方はおそらくこのページを見に来ていないでしょう。
なので、もう少しわかりやすく解説していきます。

そもそもシーケンス (sequence) とは

シーケンスがわからない方のために、“sequence” とは、一続きのもの、連続したものという意味があります。

Python におけるシーケンスというのは簡単に言うと、データが順番に連続でならんでいるもののことを言います。
例を挙げると、文字列、リスト、タプル、そして後に説明するレンジなどです。

  • リストは、任意のデータを配列の形で複数記憶するデータ型
  • タプルは、任意のデータを配列の形で複数記憶する書き換え不可能なデータ型
  • 文字列は、文字の集合体で、複数の文字の連続でできているデータ型

このように、データが順番に並んでいる状態で記憶されているオブジェクトのことをシーケンスといいます。

ジェネレータの簡単な例

ジェネレータがシーケンスを作成するオブジェクトであることはわかりました。
では、実際にジェネレータはどのようなものなのかレンジを作成する簡単な例で見ていきましょう。

こちらの例ではジェネレータ(オブジェクト)を作成するために、generator という名前のジェネレータ関数を生成します。
(このジェネレータ関数はあくまでジェネレータを作成するためだけの関数なので、普通の関数とは異なります。)


1: def generator(start, end):  # ジェネレータ関数
2:     num = start
3:     while num < end:  
4:         yield num          # [ここがポイント!] return ではなく yield になっている
5:         num += 1           # num の値をインクリメント(加算)
6: 
7: a_range = generator(1, 5)  # ジェネレータオブジェクトを実際にジェネレータ関数から生成し、変数a_range に代入
8: for i in a_range:          # for 文のようなイテレータに データソースとして生成した a_range を渡すことが出来る
9:     print(i)


この例を見ると、

  • return が、yield になっている
  • return の後ろに何かの記述がある

という点から、先ほど言ったようにジェネレータ関数が普通の関数ではないことがわかると思います。

上の例はとても簡単な例ですがこれでも十分ジェネレータオブジェクトを作ることができるジェネレータ関数です。

こちらのコードを実際に実行してみるとこのようになります。


実行結果
>>> 1
... 2
... 3
... 4
... 5 


この例、要はなにをしているかというと、range関数のようなもともとPythonに標準搭載されている組み込み関数を使わずに、独自のジェネレータオブジェクトを作成しています。

これによってfor文で用いられるrange(1, 5)のようなジェネレータオブジェクトを作成することができ、作成したジェネレータオブジェクトはfor文などで実際に使用することが出来ます。

ジェネレータは何がありがたいのか

一言でいうと、メモリの消費量を抑えることが出来るということです。

ジェネレータは、反復処理のたびに最後に呼び出されたときにどこにいたかという情報だけを管理していて、次の値を返します。
これは、以前の呼び出しについて何も記憶してないということです。

そのため、有名なところではフィボナッチ数列 などの再帰的な処理を行いたいときにこのジェネレータが機能します。
フィボナッチ数列では、時間 nのときの値を時間 n-1と、時間 n-2の値を用いて計算していきます

そこで、n = 5 の時の値が欲しい時は、
n = 4 と n = 3 の時の値が必要ですよね。

この程度ならば n = 4 と n = 3 のときの値をそれぞれ最初から計算して求めれば簡単に求められますね。

では、n = 50000 の場合はどうでしょうか。

n = 49999 と n = 49998 の値をそれぞれ一から計算しますか?
それは相当なメモリの消費量の計算になると思います。

そういった場合にジェネレータを用いると、ジェネレータは前回呼び出された時の値だけを記憶しているのでその値を使って次の計算を行うことが出来るというわけです。

このように、「こんな力任せのやり方ではメモリの消費量がすごいことになるぞ!」といったようなシーンにジェネレータが活躍します。

終わりに

ここまで見てくださりありがとうございました!

自分で書いていながら説明が難しいなと思った点などがありました。よくわからなかったという方は後半の「ジェネレータは何がありがたいのか」という部分だけでも知っていただくと良いと思います。

ジェネレータは必須知識というわけではないですが大事な知識だと思いますので習得しておきましょう。

※こちらの解説は「入門Python3」という本を参考にしました。

コメントを残す

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