ジェネレーター(Python_37)

この連載では、Pythonについて色々な形で再学習に取り組んでいます。前回の記事はこちらになります。

hirocom777.hatenadiary.org

前回は、イテレータについて学びました。イテラブル、イテレータの仕組みがわかったと思います。

今回は、ジェネレーターです。イテレータ同様繰り返しのデータを扱う仕組みのようですが、どのようなものなのでしょうか。

ジェネレーターとは

ジェネレーターとは、必要になったときに次のデータを生成する仕組みです。以下は前回ご紹介したリストからイテレータを生成して処理する例です。

it = iter([0,1,2]) # イテラブル(リスト)からイテレータを生成

print(next(it)) # 0 を返す
print(next(it)) # 1 を返す
print(next(it)) # 2 を返す
print(next(it)) # 例外 StopIteration が発生する

この例では、処理に必要なデータ(リスト)は、あらかじめ用意されています。上の例ではデータ量は少ないのですが、データ量が膨大になると、そのデータがメモリを占有してしまいます。また、データを用意するために複雑な計算を要する場合などでは、データの用意に時間がかかって、処理の開始が遅れてしまったりします。

このような時にジェネレーターを使用すると、効率のよいプログラムが書けます。以下は上記の例をジェネレーターで書き換えたものです。

def generate_test_1(end):
    for i in range(0, end):
        yield i

ge = generate_test_1(3)

print(next(ge)) # 0 を返す
print(next(ge)) # 1 を返す
print(next(ge)) # 2 を返す
print(next(ge)) # 例外 StopIteration が発生する

これを「ジェネレーター関数」といいます。通常の関数は、returnで値を返して終了となりますが、ジェネレーター関数ではyieldで値を返します。yieldが呼ばれると関数の実行は一時停止し、次に呼び出されたときにその次の場所から再開します。 これならばデータ量が多い場合でも、データがメモリを占有することはありません。データ型はジェネレーター型です。

print(type(ge)) # <class 'generator'> を返す

また、ジェネレーターは特殊メソッド「iterメソッド」、「nextメソッド」を持っています。ジェネレーターは、イテレータの一種です。

先の例ではデータ数を指定していましたが、データ数を指定せずに(無限に)値を返すジェネレーター関数も記述可能です。

def generate_numbers():
    i = 0
    while(True):
        yield i
        i += 1

ge = generate_numbers()

print(next(ge)) # 0 を返す
print(next(ge)) # 1 を返す
          ・
          ・

この例ではyieldで値を返した後、次の呼び出しでは次の文から再開します。関数の場合はreturnの後に記述された内容は無効になりますが、yieldでは有効になります。

ジェネレーター式

内包表記と似た様な記法で、ジェネレーターを生成する方法もあります。以下は、1~5の整数のうち奇数のみのリストを返すリスト内包表記です。

[i for i in range(1,6) if i % 2]

この値を順に返すジェネレーターは、以下のように記述できます。

ge = (i for i in range(1,6) if i % 2)

print(next(ge)) # 1 を返す
print(next(ge)) # 3 を返す
print(next(ge)) # 5 を返す
print(next(ge)) # 例外 StopIteration が発生する

リスト内包表記との違いは、"[]"ではなく"()"で括る点です。

無限に続くジェネレーター式

データ数を指定せずに(無限に)値を返すジェネレーター式は、「itertoolsモジュール」をインポートすることで可能になります。countオブジェクトを使用して、引数には開始する数値を指定します。引数を省略すると0になります。

import itertools

ge = (i for i in itertools.count(3))

print(next(ge)) # 3 を返す
print(next(ge)) # 4 を返す
print(next(ge)) # 5 を返す
          ・
          ・

次回はデコレーター

いかがでしょうか。ジェネレーターを使うと、効率よく繰り返しのデータを作成できます。次回はデコレーターです。関数やメソッド、クラスの定義の前に記述するのですが、どのような機能があるのでしょうか。お楽しみに!!

hirocom777.hatenadiary.org

Python再学習のまとめはこちら!!