Python | OOP:iter

Python Python
スポンサーリンク

概要(iterは「forで回せるオブジェクト」を作る入口)

iterは、そのクラスを「イテラブル(forで回せるもの)」にする特別メソッドです。戻り値は“イテレータ”で、イテレータは次の要素を返すnextを持ちます。初心者は「イテラブル=iterを持つ」「イテレータ=iternextを持つ」と覚えると整理が速いです。


基本の使い方(イテラブルとイテレータの関係)

イテレータを返す最小パターン

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
Python

yieldを使うと、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する”ジェネレータ実装。副作用は小さく、再利用性に配慮し、巨大データは逐次処理でメモリを守る。この型を身につければ、初心者でも“読みやすく、安全で効率的”な反復処理を自在に設計できます。

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