Python | OOP:イテレーター

Python Python
スポンサーリンク

概要(イテレーターは「次を返して尽きたら止まる“反復器”」)

イテレーターは「要素を一つずつ順番に返す」オブジェクトです。イテレーターは必ずiternextを持ち、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  # これだけで“正しいイテレーター”になる
Python

yieldは「次の要素」を宣言的に書ける仕組みで、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、実装はジェネレーターが最短)

イテレーターはiternextを持ち、nextが「次」を返し、尽きたらStopIterationで止めます。何度でも回したいならiterで新しいイテレーターを返す形にし、まずはジェネレーターで自然に“順送り”を書くのが最短ルート。副作用は小さく、終了条件を明確に、重い処理は切り分ける。この型を体に入れれば、初心者でも大容量・ストリーム・段階変換などの反復処理を、読みやすく安全に設計できるようになります。

タイトルとURLをコピーしました