TL;DR
Python におけるイテレータとはなんだろう?イテレータオブジェクト(list、tuple、set など)は内部にイテレータを持ち、for 文で要素を反復的に処理する際に自動的に使われる。要は、コンテナに入っている要素一つ一つアクセスして返している。
イテレータのポイント
__iter__()
と__next__()
が実装されている- コンテナに入っている要素を一つ一つアクセスして返す
- イテレータは使い終わると空になる
for i in X と書いたとき、for 文は X の iter() を呼び出し、その戻り値を利用する。この戻り値はイテレータと呼ばれる。in の後に指定されたオブジェクトには必ず iter()が適用される。
イテレータの中身
イテレータオブジェクトには、__iter__()
と__next__()
が実装されている。
実際に、iter()のプロパティを見てみると確認できる。
iter()のプロパティ
a = iter([1, 2, 3, 4])
print(dir(a))
# [...'__iter__', '__next__',...]
list のプロパティ
a = [1, 2, 3, 4]
print(dir(a))
# [...'__iter__'...]
# '__next__' は含まれない
list には__next__()
が含まれていない。つまりイテレータを生成できるが、次の要素にアクセスすることができないのである。
__iter__()
と__next__()
の違い
__iter__()
の戻り値は、そのイテレータ自身となる。反復機能を与える処理。
__next__()
は、ループのたびに呼び出される。次の要素にアクセスする処理。
for や辞書以外のオブジェクトであっても__iter__()
を実装すれば、イテレーションは可能である。例えばイテレータの機能を持つクラスを実装するには、__iter__()
を実装すればよい。
イテラブルとイテレータの違い
イテラブルという言葉も頻出するので違いを書いておく。
__iter__()
を実装しているオブジェクトは「イテラブル」と呼ばれ、__next__()
を実装しているオブジェクトは「イテレータ」と呼ばれる。
list はイテラブルであり、イテレータではない。
イテラブル
__iter__()
を実装__iter__()
の戻り値は任意のイテレータ
イテレータ
__iter__()
と__next__()
を実装__iter__()
の戻り値は self
イテレータは必ずイテラブルだが、イテラブルはイテレータとは限らない。名前が似ているのでややこしい。
list と イテレータの違い
イテレータオブジェクトは次の要素を取得できるが、list には次の要素を取得する機能はない。
欲しいときに 1 個ずつ値を取得するには、値を 1 個だけ計算する関数があればよい。list を使って最初に全ての値を用意する必要はない。イテレータの代わりに list を使うのはメモリの無駄なのだ。
イテレータは、使い終わると要素が空になる
イテレータは使い終わると空になるが、list は要素が残る。
list → 全部残る
list → イテレータ(next) → 空
iter() の作り方
実際にイテレータを作ってみる。
__iter__()
は、イテレータオブジェクトを返す。print すると <list_iterator object at 0x7fae4ab4f2d0>
と表示されている。list を使うと要素が表示された。
a = [1, 2, 3, 4]
b = iter(a)
print(b)
list(b)
# <list_iterator object at 0x7fae4ab4f2d0>
# [1, 2, 3, 4]
iter()を使うと、イテラブルなオブジェクト(iter)からイテレータ(next)を取得できる。
a = iter([1, 2, 3, 4])
next(a)
next(a)
# 2
next() の使い方
next()を使い、イテレータオブジェクトから、順に要素を取り出すことができる。
a = [1, 2, 3]
b = iter(a)
next(b)
next(b)
next(b)
# 3
__next__
__next__
メソッドを呼び出すと、イテレータは次の値を返す。ループのたびに呼び出される。返す値がなくなると、例外である StopIteration を生成する。
組み込み関数 next()にイテレータを渡すと、そのイテレータの__next__()
が呼び出され、next()の戻り値として受け取れる。つまり、next() を使ってイテレータオブジェクトすると__next__
と同じになる。
ジェネレーター
ジェネレーター(yield 文を含む関数)は、next 関数で次の値を取り出せる。yield 文によって値が一つ生成されるたびに関数は停止し、再開を待つ。
以下のコードは、next が呼ばれるまで処理が中断されている例である。
def g():
yield 1
yield 2
yield 3
a = g()
print(next(a))
print(next(a))
print(next(a))
# 1
# 2
# 3
# StopIteration → 値がなくなると例外
next()を__next__()
に変えても同じ結果になる。
def g():
yield 1
yield 2
yield 3
a = g()
print(a.__next__())
print(a.__next__())
print(a.__next__())
# 1
# 2
# 3