概要(nextは「次の要素」を返し、尽きたらStopIterationで終了する)
nextは“イテレータ”が持つ特別メソッドで、呼ぶたびに次の要素を返します。要素がもうないときはStopIterationを送出して、反復を終わらせます。for文や内蔵関数next()の裏側で必ず使われるため、反復処理の芯になる仕組みです。初心者は「イテラブル=iterを持つ」「イテレータ=iterとnextを持つ」と覚えると整理が速いです。
基本の使い方(イテレータの定義とnext()の動き)
最小のイテレータ実装
class Countdown:
def __init__(self, start: int):
self.cur = start # 3なら 3, 2, 1 を返す
def __iter__(self):
return self # 自分自身がイテレータ
def __next__(self) -> int:
if self.cur <= 0:
raise StopIteration
v = self.cur
self.cur -= 1
return v
c = Countdown(3)
print(next(c)) # 3
print(next(c)) # 2
print(next(c)) # 1
# print(next(c)) # StopIteration(尽きたら終了)
Pythonnext(obj)は内部でobj.next()を呼びます。要素が尽きたら必ずStopIterationを送出するのが反復の合図です。
for文とnextの関係
for x in Countdown(3):
print(x) # 3, 2, 1 を順に表示
Pythonforは、イテラブルからイテレータを取得し、nextを繰り返し呼び、StopIterationでループを終わらせます。
イテラブルとの連携(iterで“新しいイテレータ”を返す)
イテラブルとイテレータを分けて設計する
class RangeLike:
def __init__(self, start: int, stop: int):
self.start = start
self.stop = stop
def __iter__(self):
return RangeIter(self.start, self.stop)
class RangeIter:
def __init__(self, start: int, stop: int):
self.cur = start
self.stop = stop
def __iter__(self):
return self
def __next__(self) -> int:
if self.cur >= self.stop:
raise StopIteration
v = self.cur
self.cur += 1
return v
r = RangeLike(2, 5)
print(list(r)) # [2, 3, 4]
print(list(r)) # 何度でも回せる(毎回新しいイテレータを返す)
Python同じオブジェクトを何度でもforで回したいなら、iterは“毎回新しいイテレータ”を返すのが安全です。
ジェネレータで簡潔に書く(nextを自分で実装しない近道)
yieldを使った最短の反復定義
class Lines:
def __init__(self):
self._buf: list[str] = []
def add(self, s: str):
if "\n" in s:
raise ValueError("1行に改行不可")
self._buf.append(s)
def __iter__(self):
for s in self._buf:
yield s # ジェネレータ:__next__とStopIterationは自動処理
Pythonyieldを使えば、nextやStopIterationを明示せず、自然な反復が書けます。最初に使うならジェネレータが一番わかりやすいです。
設計の要点(終了条件・副作用・再利用性)
終了条件を明確にする
“尽きたらStopIteration”が反復の契約です。値がなくなった瞬間に迷わずraise StopIterationして、呼び手(forやnext)が自然に終了できるようにします。
副作用は小さく、予測可能にする
nextの中で重いI/Oや不規則な副作用を入れると、1ステップのコストが読めなくなります。ログや軽い検証は許容範囲ですが、接続や書き込みは明示メソッドに分離する方が安全です。
再利用性を意識する
“自分自身がイテレータ”の設計は、一度回し切ると空になります。何度も回したい用途なら、毎回新しいイテレータを返すか、ジェネレータで定義するのが適します。
実務での使いどころ(ページング・ストリーム・変換)
ページングAPIを順次要素にする
import requests
class Users:
def __init__(self, base_url: str):
self.base_url = base_url
def __iter__(self):
page = 1
while True:
r = requests.get(f"{self.base_url}/users", params={"page": page}, timeout=5.0)
r.raise_for_status()
data = r.json().get("items", [])
if not data:
break
for u in data:
yield {"id": str(u.get("id", "")), "name": u.get("name") or "unknown"}
page += 1
Python巨大データでも“逐次返す”設計にすると、メモリ効率と見通しが良くなります。
ストリーム変換を段階的につなぐ
class NormalizedUsers:
def __init__(self, source):
self.source = source # イテラブル(Usersなど)
def __iter__(self):
for u in self.source:
yield {"id": u["id"], "name": u["name"].strip()}
Pythonイテラブルからイテラブルへ、段階的に変換する構成は、安全で拡張しやすいです。
よくある落とし穴(StopIterationの誤用・例外の握りつぶし・無限反復)
StopIterationを忘れて無限ループ化する
終了条件を満たしたら必ずStopIterationを送出してください。returnではなくraise StopIterationが契約です(ジェネレータはreturnで自動的に終了します)。
例外を握りつぶして欠損データにする
next内のエラーを無視すると、原因不明の欠損になります。必要ならドメイン例外へラップして明確に伝えるか、ログを残して止める設計にします。
無限反復の管理を怠る
“終わらない”イテレータも実用的ですが、呼び手側でbreak条件や件数制限を必ず設けましょう。テストでは上限を付けて検証します。
例題(ファイルの非空行を逐次返す安全なイテラブル)
逐次処理でメモリを守り、終了も確実に
class FileLines:
def __init__(self, path: str, encoding: str = "utf-8"):
self.path = path
self.encoding = encoding
def __iter__(self):
with open(self.path, "r", encoding=self.encoding) as f:
for line in f:
s = line.rstrip("\n")
if not s:
continue
yield s
Python巨大ファイルでも一行ずつ処理でき、withに閉じ込めることで資源のクリーンアップも自動化できます。
まとめ(nextは反復の心臓。終了はStopIteration、実装はジェネレータが近道)
nextは“次の要素”を返し、尽きたらStopIterationで終える反復の心臓です。イテラブルはiterでイテレータを渡し、イテレータはiterとnextを持つ。最短で実用的なのは、iterでyieldするジェネレータ設計。副作用を小さく、終了条件を明確に、再利用性に配慮すれば、初心者でも“安全で効率的”な反復処理を自在に構築できます。
