Python : __iter__()はイテレータを返し、__next__()は次の要素を返す

TwitterFacebookHatena
  • 公開:2021-8-30
  • 更新:2023-10-26
  • 文章量:2479
  • Python

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

Python : __iter__()はイテレータを返し、__next__()は次の要素を返す