概要(iterは「forで回せるオブジェクト」を作る入口)
iterは、そのクラスを「イテラブル(forで回せるもの)」にする特別メソッドです。戻り値は“イテレータ”で、イテレータは次の要素を返すnextを持ちます。初心者は「イテラブル=iterを持つ」「イテレータ=iterとnextを持つ」と覚えると整理が速いです。
基本の使い方(イテラブルとイテレータの関係)
イテレータを返す最小パターン
class Numbers:
def __init__(self, n: int):
self.n = n # 0..n-1 を返す
def __iter__(self):
return NumbersIter(self.n) # イテレータを返す
class NumbersIter:
def __init__(self, n: int):
self.n = n
self.i = 0
def __iter__(self):
return self # 自分自身がイテレータ
def __next__(self) -> int:
if self.i >= self.n:
raise StopIteration
v = self.i
self.i += 1
return v
nums = Numbers(3)
for x in nums: # 0, 1, 2
print(x)
Pythonイテラブル(Numbers)はiterで“イテレータ”を返し、イテレータ(NumbersIter)はnextで要素を順に返し、尽きたらStopIterationで終了します。
自分自身をイテレータにする簡易パターン
class Countdown:
def __init__(self, start: int):
self.cur = start
def __iter__(self):
return self
def __next__(self) -> int:
if self.cur <= 0:
raise StopIteration
self.cur -= 1
return self.cur + 1
for x in Countdown(3): # 3, 2, 1
print(x)
Python小さな一発イテレータなら、「イテラブル=イテレータ」を同一クラスで持つのが手軽です。
ジェネレータで書く(iterに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に置き換えても同じ
yield s
Pythonyieldを使うと、nextを自分で書かずに「順に返す」ふるまいを自然に表現できます。停止は自動で処理され、可読性と保守性が高いのが利点です。
設計の要点(再利用性・副作用・メモリ効率)
再利用性(新しいイテレータを返す)
同じオブジェクトを何度もforで回せるように、iterは“毎回新しいイテレータ”を返すのが安全です。自分自身をイテレータにする場合、1周した後は再度使えないことが多い点に注意します。
副作用は小さくする
nextやyieldの中で重いI/Oや破壊的更新を入れすぎると、ループの1ステップが予測不能になります。ログや軽い検証はOKですが、接続・書き込みなどは明示メソッドに分離しましょう。
メモリ効率を意識する
巨大データは「逐次返す」ジェネレータ設計にすると、全件を一度にメモリへ載せずに処理できます。行読み、ページングAPIなどで効果が大きいです。
Web / APIの実例(ページング・ストリーミング・変換)
ページング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})
r.raise_for_status()
data = r.json()
items = data.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「受け取ったイテラブルを変換して返す」ことで、処理を安全に段階分割できます。
よくある落とし穴と回避(StopIteration・使い回し・例外設計)
StopIterationを正しく使う
要素が尽きたら必ずStopIterationを送出します。while Trueでnextを書いているときは、終了条件で躊躇なくraiseを呼ぶこと。
使い回しできないイテレータ
同じイテレータを二度回すと空になります。再利用したいなら、iterで“新しいイテレータを返す”か、ジェネレータにして毎回新しく生成されるようにします。
例外を飲み込まない
取得中のValueErrorやHTTPErrorを握りつぶすと、原因不明の欠損になります。必要ならラップしてドメイン例外に変換し、ログを残す設計にしてください。
例題(テキストファイルの行を安全に逐次処理する)
ファイルを逐次読み、空行をスキップして返す
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巨大ファイルでもメモリ効率良く、forで自然に処理できます。withに閉じ込めることで、資源のクリーンアップも確実です。
まとめ(iterで「順に返す」を型にする。ジェネレータで素直に書く)
iterを実装すれば、あなたのクラスは「forで自然に回せる」イテラブルになります。イテレータはnextで1つずつ返し、尽きたらStopIteration。最短で実用的なのは“iterでyieldする”ジェネレータ実装。副作用は小さく、再利用性に配慮し、巨大データは逐次処理でメモリを守る。この型を身につければ、初心者でも“読みやすく、安全で効率的”な反復処理を自在に設計できます。
