Python イテレータ ジェネレータとは?

イテレータとはリストやタプルのようなシーケンスを扱う手法の一つです。

シーケンスを扱うときは、for文を使って先頭の要素から順番に処理をしていくことが多いです。先頭の要素を処理し終えた後は、2つ目の要素の処理をし、3つ目、4つ目・・・と処理が続きます。

イテレータではこのように「次の要素を取り出す」という処理を繰り返します。

Pythonではイテレータオブジェクトと呼ばれるオブジェクトを使ってイテレータを実現しています。以下の特徴があります。

  1. 次の要素を返す__next__メソッドを必ずサポートしている
  2. データストリームに要素が残っていない場合、 __next__メソッドはStopIteration 例外を出す
  3. 有限長だけでなく、無限長のイテレータも生成可能
  4. リストや辞書、タプルはイテレータ化可能
  5. リストは要素番号で要素を取得できますが、イテレータはできない
  6. 遅延評価

__next__メソッドを持つ

nextメソッドにイテレータ を渡すと、内部で__next__メソッドが呼ばれます。__next__メソッドは直接使いません。

nextメソッドの引数にイテレータを渡して、要素を一つづつ取得します。

以下のように使えますがnextメソッドを使うことは滅多にありません。

L = [1, 2]
it = iter(L)
print(next(it))
print(next(it))
print(next(it))

【結果】

1
2
Traceback (most recent call last):
File "test.py", line 3, in 
    print(next(it))
StopIteration

リストや辞書、タプルはイテレータ化可能

iter関数にリストやタプルを渡すと、イテレータを取得できます。実際にリストをイテレータ化する必要性は滅多にありません。

nums = [1,2,3]
iterator = iter(nums)
print(next(iterator))

【結果】

1

イテレータをlistまたはtupleのコンストラクタで呼び出すことにより、すべてメモリにインスタンス化できます。

イテレータの要素を要素番号で取得して処理したいような場合は、リスト化する必要があります。使用頻度は割と高いです。

nums = [1,2,3]
iterator = iter(nums)
t = tuple(iterator)
print(t)

【結果】

(1, 2, 3)

遅延評価

イテレータは必要になる時まで、次の要素を用意しません。

このようなことを遅延評価(遅延実行)といいます。これを使うと、実際にはほとんどのデータをメモリに保存しないで、大量のデータを意味的に操作できます。

これに対して、リストはデータをメモリに確保しているため、いつでも要素番号を指定すれば欲しい要素が得られます。

遅延評価は関数型プログラミングの特徴の一つで重要です。

ジェネレータ

ジェネレータとはイテレータを簡単に作成するための仕組みです。

通常の関数はreturnでデータを返しますが、ジェネレータはyield文で返します。

次のreverse関数は与えられたdataを逆順に返すイテレータを返す関数です。

def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]
    for char in reverse('one'):
        print(char)

【結果】

e
n
o

ジェネレータを使って簡単にイテレータを作成できていることがわかります。イテレータなのでnext()を呼び出して次の要素を取得することも可能です。

i = reverse('one')
print(next(i))
print(next(i))
print(next(i))

【結果】

e
n
o

ジェネレータ式

ジェネレータ式とはリスト内包表記のような記法を使って定義するジェネレータのことです。

記法

( 式 for 変数 in シーケンス if文 )

if文は任意です。両端の丸カッコは、以下のように関数を呼び出すときの丸カッコでも問題ないです。

sum(i*i for i in range(10))   # 1から9の2乗のリストの合計

以下のようにリスト内包表記を使って求めることも可能ですが、リスト分のメモリを確保します。

リストが必要なのか、イテレータで十分なのか状況に応じて使い分けられるようになりましょう。

L = [i*i for i in range(10)]
sum(L)

itertools

itertoolsというイテレータ系のモジュールがあります。

備わっている機能は難しくなく、量も多くはないです。一通り目を通してどんな機能があるか把握しておくと良いです。itertoolsは次のような特徴があります。

大まかな機能

  • 新しいイテレータを作る関数
  • 要素に対して関数を呼ぶ
  • 要素を選択する
  • 組み合わせ関数
  • 要素をグループ分けする

出力されるイテレータの種類

  • 無限長のイテレータ
  • 一番短い入力シーケンスで止まるイテレータ
  • 組み合わせのイテレータ

Itertoolsのレシピにはitertoolsを使って機能を拡張する例が載っています。少々分かりづらいですが、応用の仕方が分かって参考になるのでおすすめです。

スポンサーリンク
スポンサーリンク