Python __next__とは?イテレータを自作する方法とStopIteration

__next__は、イテレータが「次の値」を返すための特殊メソッドです。

next()関数やfor文の1周ごとに呼ばれ、終わりはStopIterationで伝えます。


__next__の基本

class Lines:
    def __init__(self, text):
        self.rows = text.splitlines()
        self.i = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.i >= len(self.rows):
            raise StopIteration
        row = self.rows[self.i]
        self.i += 1
        return row

it = Lines("a\nb")

print(next(it))
print(next(it))
next(it)

実行結果:

a
b
StopIteration

イテレータには2つのメソッドが必要です。

  • __next__ —— 次の値を返す
  • __iter__ —— selfを返す

__iter__を忘れるとfor文が動かない

class OnlyNext:
    def __next__(self):
        return 1

for x in OnlyNext():
    print(x)

実行結果:

TypeError: 'OnlyNext' object is not iterable

for文は最初にiter()を呼ぶので、__next__だけでは足りません。

逆に__iter__だけでも失敗します。

class NoNext:
    def __iter__(self):
        return self

next(NoNext())

実行結果:

TypeError: 'NoNext' object is not an iterator

「is not an iterator」は__next__が無いという意味です。「is not iterable」(__iter__が無い)と1語違いで意味が違うので読み分けてください。


StopIterationを出さないと止まらない

class Never:
    def __iter__(self):
        return self

    def __next__(self):
        return 1

list(Never())     # ← 終わらない

StopIterationを投げない限り、for文は永久に回り続けます。

途中で打ち切って確認するとこうなります。

import itertools

print(list(itertools.islice(Never(), 3)))

実行結果:

[1, 1, 1]

無限イテレータ自体は正当な設計です(itertools.count()など)。止める気があるなら必ずraise StopIterationを書いてください。


next()には既定値を渡せる

it = Lines("")

print(next(it, "終わり"))

実行結果:

終わり

第2引数を渡すと、StopIterationの代わりにその値が返ります。

tryで囲む必要がなくなるため、1つだけ取り出したいときに便利です。


普通はyieldで書けばよい

ここまでの内容を、yieldならこう書けます。

class Lines:
    def __init__(self, text):
        self.text = text

    def __iter__(self):
        for row in self.text.splitlines():
            yield row

print(list(Lines("a\nb")))
print(list(Lines("a\nb")))

実行結果:

['a', 'b']
['a', 'b']

位置を管理する変数もStopIterationも不要になり、しかも何度でも回せます。

手書きの__next__が必要なのは、次のような場合に限られます。

  • 外部から1つずつ取り出す制御が必要
  • ファイルや通信のように本質的に使い捨て
  • イテレータ自体にメソッドや属性を持たせたい

それ以外は__iter__とyieldで十分です。「Python __iter__とは?for文で回せるクラスを作る方法」を参照してください。


ジェネレータのreturnはStopIterationになる

def g():
    yield 1
    return "戻り値"

it = g()
print(next(it))

try:
    next(it)
except StopIteration as e:
    print("value:", e.value)

実行結果:

1
value: 戻り値

ジェネレータのreturnは、StopIterationのvalue属性として運ばれます。

for文では捨てられるので、通常は気にする必要はありません。


__next__のまとめ

  • 次の値を返す——next()とfor文から呼ばれる
  • __iter__でselfを返すのがセット
  • 「is not an iterator」=__next__が無い
  • StopIterationを出さないと無限ループ
  • next(it, 既定値)で例外を避けられる
  • 大半の用途はyieldのほうが簡単で安全

あわせて読みたい

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら