Pythonでジェネレータ(yield)の使い方やなぜ使うと良いのかを解説する

スポンサーリンク

Pythonのジェネレータは、オブジェクトのシーケンスを生成する必要がある場合、どこでも使用できるカスタムイテレータを作成するための強力なツールです。

スポンサーリンク

前提条件

Pythonでジェネレータを続ける前に、2つの重要な概念を知っておく必要があります。

1. イテラブル

イテラブルとは、iter() が呼ばれるとイテレータを返すオブジェクトのことです。

言い換えると、他のオブジェクトのシーケンスであるオブジェクトは、典型的なイテラブルです。

この例を見てください。

numbers = list([1, 2, 3, 4])
for element in numbers:
    print(element)

ここで、 number は整数の並びです。

このオブジェクトに対して iter() を呼び出すと、”list_iterator” が返されます。

これが for ループの中で直接使用できる理由です。

実際、 list, dictionary, set, tuple はすべてイテレート可能なクラスです。

def perfect_square():
    num = 1
    while(num <= 10):
        yield (num * num)
        num += 1

さて、イテレータを手に入れたら、どうしましょうか?

2. イテレータ

イテレータは iter() が返すオブジェクトです (上で見たように)。

また、このチュートリアルで学習するジェネレータを使ってプログラマが作ることもできます。

イテレータは3つの重要な特性を持っています。

    1. next()が呼ばれるとオブジェクトを返す。
    1. 返すべきオブジェクトがない場合、 StopIteration エラーを発生させます。
    1. イテレーションは一度だけ行われます。もし、5つの数字を含むリストに対してイテレータを取得し、 next() を4回呼び出したとしたら、もう1回だけ next を呼び出すことができ、それ以降はイテレータは何の役にも立たない。つまり、同じリストに対して繰り返し処理を行うには、新しいイテレータが必要になるのです。

この例で考えてみましょう。

for square in perfect_squares():
    print(square)

Pythonのジェネレータって何?

Pythonのジェネレータはイテレータを生成する関数です。

ジェネレータは関数と同じ構文に従いますが、何かを返す必要があるときは return の代わりに yield と記述します。

ジェネレータ関数の作成

例えば、1 から始まる最初の 10 個の完全平方根を生成する必要があるとしよう。

これは構文です。

squares_list = [num * num for num in range(1,11)]

コードを一行ずつ見ていきましょう。

  • def perfect_square(): 関数ブロックの通常の開始部分です。
  • num = 1: 完全二乗を生成するために必要な唯一のメモリ。
  • while(num &lt;= 10): while(num <= 10)`: 10個の完璧な正方形を生成する必要があります。
  • yield(num * num): Pythonの通常の関数との最も重要で顕著な違いです。これは生成された完全な正方形を返すという点で、return文に似ています。この関数が返す完全な正方形はすべて生成されたもので、メモリから取得されたものではないことに注意してください。
  • num += 1: 次の正方形を生成するためにインクリメントしています。

このジェネレータの動作を見てみましょう。

単に関数のように呼び出すと、generator object が返されます。

squares_list = (num * num for num in range(1,11))

このオブジェクトを使うことになる。

このオブジェクトに対して next() を呼び出すと最初の値が得られ、もう一度 next() を呼び出すと2番目の値が得られ、以下10番目の値まで同じように得られます。

その後、next() を呼び出すと別の値を得ようとしますが、関数が終了しているため、StopIteration エラーを発生させます。

Iterator Example

ループの最後で例外をチェックすることもできますが、for-loop はすでにそれを行っています。

for ループは範囲、リスト、タプルなどの反復子を受け入れることができることを思い出してください。

同様に、for-loopはジェネレータも受け付ける。

Iterator Next Example
Num_iterator is an iterator of a list (or a list_iterator), and next() is called five times on the iterator. The iterator returns the first four numbers in the list, but after that, it raises a stop iteration error.

上のコードは前と全く同じものを表示します。

自分で試してみてください。

イテレータのように、ジェネレータオブジェクトはリサイクルできないので、 squares (今回使用したジェネレータオブジェクト) で終了した後は、単に squares = perfect_squares() をもう一度実行して、別のオブジェクトを取得する必要があることに注意しましょう。

また、ジェネレータ関数とジェネレータオブジェクトは異なるものであることに注意してください。

ジェネレータ関数(または単にジェネレータ)は、必要なすべての値を生成するジェネレータオブジェクトを返すために使用されます。

ジェネレータ式

もっとシンプルなジェネレータを作るには、ジェネレータ式を使います。

リスト内包を思い出してください。

最初の10個の完全な正方形を含むリストを作るには、次のようにします。

Generator Object
Squares is a generator object

と”]”を”(“と”) “に置き換えることで、これらの値を生成するジェネレーターが作成されます。

Calling Next On Generator
Calling Next On Generator

リストはメモリに保存され、いつでもアクセスできるが、ジェネレータは一度しか使えないことに注意しよう。

なぜジェネレータが必要なのか?

この2つのサイズの違いを見てみましょう。

モジュール sys をインポートして、 sys.getsizeof() を実行すると、2つのオブジェクトのサイズが得られます。

以下のようになります。

  • squares_list: 184 B
  • squares_generator。112 B

これは大きな違いではありません。

でも、100個必要な場合はどうなるかというと、サイズが変わります。

  • squares_list: 904 B
  • squares_generator: 112 B

10000個なら

  • squares_list。87616 B or 85.5 KB
  • squares_generator: 112 B


最初の100万個のフィボナッチ数やグラフを表示する関数の値のような巨大な列が必要で、それが一度か二度しか必要ない場合、ジェネレータは多くの時間(コーディング)とスペース(メモリ)を節約できることは明らかだ。

参考文献

Python Wiki – ジェネレータ

タイトルとURLをコピーしました