概要(イテレーターは「次を返して尽きたら止まる“反復器”」)
イテレーターは「要素を一つずつ順番に返す」オブジェクトです。イテレーターは必ずiterとnextを持ち、nextは次の要素を返し、もう要素がないときにStopIterationを送出して反復を終了します。for文や内蔵のnext()は、この仕組みを土台に動いています。初心者は「イテラブル(forで回せる)」「イテレーター(次を返せる)」をはっきり分けて覚えると整理が早いです。
基本の使い方(iterとnextで“順送り”を作る)
最小のイテレーター(自分自身がイテレーター)
class Countdown:
def __init__(self, start: int):
self.cur = start # 例: start=3なら 3, 2, 1 を返す
def __iter__(self): # forが求める“イテレータを返す”窓口
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
# next(c) # StopIteration(もう終わり)
Pythonこのように、呼ぶたびに「次」を返し、終わりでStopIterationを送るのがイテレーターの契約です。forはこの契約に従って自動で止まります。
イテラブルとイテレーターを分ける(何度でも回せる設計)
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)) # 再度OK(新しいイテレータが返る)
Python「同じオブジェクトを何度もforで回したい」場合は、iterで毎回新しいイテレーターを返す形が安全です。
ジェネレーターで書く(最短ルートで正しいイテレーション)
iterでyieldするだけ(nextは自動生成される)
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 # これだけで“正しいイテレーター”になる
Pythonyieldは「次の要素」を宣言的に書ける仕組みで、StopIterationは自動処理されます。まずはジェネレーターで感覚を掴むのが近道です。
重要ポイントの深掘り(終了条件・再利用性・副作用の扱い)
終了条件はStopIterationで明示する
値が尽きたら必ずStopIterationを送出します。これが呼び手(forやnext)の「止まる合図」になります。ジェネレーターでは、自然にループを抜けるかreturnで終了すれば自動的にStopIterationになります。
再利用性を意識して設計する
「自分自身がイテレーター」だと、一度回し切った後は空になります。何度も回す必要があるなら、毎回新しいイテレーターを返すか、yieldで定義して“都度生成”にします。
副作用は軽く、予測可能に
next内で重いI/Oや複雑な状態変更を入れすぎると、ループの一歩が「何が起きるかわからない」ものになります。ログや軽い整形に留め、外部通信や書き込みは明示メソッドへ分離すると、意図が伝わりやすく事故も減ります。
実務例(大容量処理・ページングAPI・段階的変換)
大きなファイルを“逐次”処理してメモリを守る
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で資源の管理も自動化できます。
ページングAPIを“自然なfor”で扱う
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()
items = r.json().get("items", [])
if not items:
break
for u in items:
yield {"id": str(u.get("id", "")), "name": u.get("name") or "unknown"}
page += 1
Python呼び手はforで“全部のユーザー”を扱え、内部ではページごとに取得するためメモリ効率が良好です。
段階的な変換で安全に整える
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イテラブルからイテラブルへバトンを渡す構成は、責務分離が明確で拡張もしやすくなります。
よくあるつまずきと回避(無限反復・例外の握りつぶし・遅すぎるステップ)
無限反復の管理不足
終わらないイテレーターは実用的ですが、呼び手側でbreak条件や最大件数を必ず設けましょう。テスト時も上限を付けて検証します。
例外を握りつぶして欠損にする
nextの中で起きたエラーを無視すると、原因不明の穴が空きます。必要ならドメイン例外へラップして伝え、ログを記録する設計にします。
1ステップが重すぎる
nextに高コスト処理を入れると、ループ全体の体感が悪化します。重い部分はバッチ化、先読みキャッシュ、外部の明示的メソッドへ切り出して、反復の“歩幅”を軽く保ってください。
例題(カスタム範囲・フィボナッチ・バッチ読取りのイテレーター)
カスタム範囲(奇数だけを返す)
class OddRange:
def __init__(self, start: int, stop: int):
self.start = start if start % 2 else start + 1
self.stop = stop
def __iter__(self):
cur = self.start
while cur < self.stop:
yield cur
cur += 2
print(list(OddRange(3, 12))) # [3, 5, 7, 9, 11]
Pythonフィボナッチの先頭N項
class Fibonacci:
def __init__(self, n: int):
self.n = n
def __iter__(self):
a, b, i = 0, 1, 0
while i < self.n:
yield a
a, b = b, a + b
i += 1
print(list(Fibonacci(7))) # [0, 1, 1, 2, 3, 5, 8]
Pythonバッチ読み(まとめて取得しつつ逐次返す)
class Batched:
def __init__(self, fetch_fn, batch_size: int = 100):
self.fetch_fn = fetch_fn
self.batch_size = batch_size
def __iter__(self):
offset = 0
while True:
batch = self.fetch_fn(offset, self.batch_size)
if not batch:
break
for item in batch:
yield item
offset += len(batch)
Pythonこの型なら、大量データを適切な塊で取りつつ、呼び手には“逐次”として見せられます。
まとめ(イテレーターは反復の心臓。終了はStopIteration、実装はジェネレーターが最短)
イテレーターはiterとnextを持ち、nextが「次」を返し、尽きたらStopIterationで止めます。何度でも回したいならiterで新しいイテレーターを返す形にし、まずはジェネレーターで自然に“順送り”を書くのが最短ルート。副作用は小さく、終了条件を明確に、重い処理は切り分ける。この型を体に入れれば、初心者でも大容量・ストリーム・段階変換などの反復処理を、読みやすく安全に設計できるようになります。
