イテレータとジェネレータ

イテレータとジェネレータ

イテレータ

 Python における関数型スタイルのプログラミングの核となる機能が イテレータ です。イテレータとは要素を順番に取り出すことのできるオブジェクトです。Python では for文でループ処理を実行するときに、リストやタプルを自動的にイテレータに変換しています。

 イテレータに変換できるオブジェクトのことを イテラブル・オブジェクト (iterable object) とよびます。リスト、タプル、ディクショナリなどはイテラブルです。イテラブル・オブジェクトをイテレータに手動で変換するときは組み込み関数の iter() を使います。

# リストを定義
my_list = ["電子", "陽子", "中性子"]

# リストをイテレータに変換
it = iter(my_list)

# イテレータから要素を取り出す
a = next(it)

print(a)
電子

 上のコードにあるように、イテレータから要素を取り出すときには next()関数 を使います。イテレータは前に取り出された要素のインデックスを記憶していて、next()関数を使うたびに次の要素を取り出します(イテレータは内部に __next__() という、次の要素を取り出すメソッドを備えています)。ある意味、イテレータは極めて単純な機能しかもたないオブジェクトです。イテレータにできることは次に進むことだけです。前の要素を取り出すこともできないし、自身のコピーをとることもできません。

 すべての要素を取り尽くしたあとに、もう一度 next() で要素を取り出そうとすると、StopIteration という例外を発生し、イテレータに要素が残っていないことを知らせます。

# リストを定義
my_list = ["電子", "陽子", "中性子"]

# リストをイテレータに変換
it = iter(my_list)

# イテレータから要素を取り出す
next(it)
next(it)
next(it)
next(it)
StopIteration
Traceback (most recent call last)
 in ()
      9 next(it)
     10 next(it)
---> 11 next(it)
StopIteration:

 

ジェネレータ

 ジェネレータとはイテレータを定義するための特殊な関数です。

 普通の関数は呼び出されたときにローカル変数が作られてから、関数内部の命令文によって変数が操作され、return文のところで値を返すと、その変数は破棄されます。そして関数が再び呼び出されると、また新しいローカル変数が作り直されて処理を実行します。

 これに対してジェネレータは return の代わりに yield という命令文でローカル変数を返して処理を終了します。その際にジェネレータはローカル変数を破棄せずにメモリに残しておきます。次に呼び出されたときには、その変数を使って処理を続行するのです。以下に簡単なジェネレータを定義してみます。

# ジェネレータの定義
def gen(x):
    for k in x:
        yield k

# リストの定義
my_list = ["電子", "陽子", "中性子"]

# ジェネレータにリストを渡してイテレータを作成
it = gen(my_list)

print(next(it))
print(next(it))
print(next(it))
電子
陽子
中性子

 yield を含む関数はすべてジェネレータとして扱われます。Python が yield文 を検出すると特殊な方法でコンパイルするような仕組みになっています。サンプルコードにあるように、ジェネレータ自体は呼び出されてもイテレータを返すだけです。このイテレータに対して next() が使用されたときに初めて、関数内部に定義した処理内容が実行されるという仕組みになっています。

 ジェネレータはジェネレータ式とよばれる記法を使って、より簡単に定義することができます:

 (式 for 繰り返し変数 in イテラブル)

 内包表記と似ていますが、内包表記は [ ] で括るのに対して、ジェネレータ式は ( ) で括って定義します。さきほどのサンプルコードをジェネレータ式を使って書き直すと次のようになります。

# リストの定義
my_list = ["電子", "陽子", "中性子"]

# ジェネレータ式
it = (x for x in my_list)

print(next(it))
print(next(it))
print(next(it))
電子
陽子
中性子

 タプルのすべての要素の平方根をとるというジェネレータをジェネレータ式で定義してみます。

# タプルの定義
my_tup = (1, 4, 9, 16, 25)

# ジェネレータ式
it = (x**0.5 for x in my_tup)

print(next(it), next(it), next(it))
1.0 2.0 3.0