Python イテレータパターン

想像してみてください。色とりどりのキャンディが詰まった瓶があります。一粒ずつ味わいたいが、一度に全部のキャンディを取り出したくはありません。そんなとき、瓶に手を伸ばして一粒だけ取り出すでしょう。このプロセスが反復(イテレーション)です。

プログラミングにおいて、イテレータパターンはデザインパターンの一つで、集合オブジェクトの内部表現を公開することなく、その要素に順番にアクセスする方法を提供します。

中核概念

イテレータパターンは2つの主要な構成要素を含みます。

  1. イテレータ (Iterator):要素へのアクセスと走査を定義するインターフェースを担当します
  2. イテラブル (Iterable):イテレータを作成するメソッドを提供します

Pythonでは、イテレータパターンは言語のコア機能に深く統合されており、プログラミングをよりエレガントで直感的にしています。


なぜイテレータが必要か?

従来方式の限界

まずイテレータを使わない例を見てみましょう。

例

# シンプルな書籍コレクションクラス
class BookCollection:
    def __init__(self):
        self.books = ["Python入門", "アルゴリズム入門", "デザインパターン", "データ構造"]
   
    def get_books(self):
        return self.books

# このコレクションを使用
collection = BookCollection()
books = collection.get_books()

# 書籍を走査 - 内部実装の詳細を公開している
for i in range(len(books)):
    print(books[i])

問題分析:

  • クライアントコードはコレクションの内部構造(リスト)を知る必要がある
  • コレクションの内部実装が変更されると(例えばリストから辞書に変更)、すべてのクライアントコードを修正する必要がある
  • 走査ロジックがコレクション実装と密結合している

イテレータの利点

例

# イテレータを使う方法
for book in collection:
    print(book)

イテレータがもたらす利点:

  • カプセル化:コレクションの内部実装を隠す
  • 統一インターフェース:異なるコレクション型でも同じ走査方法を使用できる
  • 柔軟性:コレクションの実装方法を簡単に変更できる
  • 複数の走査に対応:複数の走査操作を同時に実行できる

Pythonにおけるイテレータプロトコル

Pythonは2つの特別なメソッドによってイテレータプロトコルを実装しています。

__iter__()メソッド

  • イテレータオブジェクトを返す
  • イテラブルはこのメソッドを実装する必要がある

__next__()メソッド

  • シーケンス内の次の要素を返す
  • 要素がもうないとき、スローするStopIteration例外

フローチャートでイテレータの動作メカニズムを理解しましょう。


カスタムイテレータの作成

方法1: クラスを使用してイテレータを実装する

カスタムの書籍イテレータを作成しましょう。

例

class BookIterator:
    """書籍イテレータクラス"""
   
    def __init__(self, books):
        self.books = books
        self.index = 0
   
    def __iter__(self):
        """イテレータ自身を返す"""
        return self
   
    def __next__(self):
        """次の本を返す。本がもうない場合はStopIterationをスローする"""
        if self.index < len(self.books):
            book = self.books[self.index]
            self.index += 1
            return book
        else:
            raise StopIteration

class BookCollection:
    """イテラブルな書籍コレクションクラス"""
   
    def __init__(self):
        self.books = ["Python入門", "アルゴリズム入門", "デザインパターン", "データ構造"]
   
    def __iter__(self):
        """イテレータインスタンスを返す"""
        return BookIterator(self.books)

# カスタムイテレータを使用
collection = BookCollection()
for book in collection:
    print(f"読書中: {book}")

出力結果:

正在阅读: Python入门
正在阅读: 算法导论
正在阅读: 设计模式
正在阅读: 数据结构

方法2: ジェネレータ関数を使用する

Pythonはより簡潔な方法、ジェネレータ関数を提供しています。

例

class BookCollection:
    def __init__(self):
        self.books = ["Python入門", "アルゴリズム入門", "デザインパターン", "データ構造"]
   
    def __iter__(self):
        """ジェネレータ関数を使用してイテレータを作成"""
        for book in self.books:
            yield book

# 使用方法はまったく同じ
collection = BookCollection()
for book in collection:
    print(f"読書: {book}")

ジェネレータの利点:

  • コードがより簡潔
  • 状態保存を自動処理
  • パフォーマンスが良い

イテレータの実際の応用シーン

シナリオ1: ページングデータの読み取り

例

class PaginatedData:
    """ページングデータイテレータのシミュレーション"""
   
    def __init__(self, total_items, page_size=3):
        self.total_items = total_items
        self.page_size = page_size
        self.current_page = 0
   
    def __iter__(self):
        return self
   
    def __next__(self):
        start = self.current_page * self.page_size
        end = start + self.page_size
       
        if start >= self.total_items:
            raise StopIteration
       
        # データベースから1ページ分のデータを読み取るシミュレーション
        page_data = list(range(start, min(end, self.total_items)))
        self.current_page += 1
       
        return page_data

# ページングイテレータを使用
paginator = PaginatedData(10, 3)  # 合計10件のデータ、各ページ3件
for page_num, page_data in enumerate(paginator, 1):
    print(f"第{page_num}ページのデータ: {page_data}")

出力結果:

第1页数据: [0, 1, 2]
第2页数据: [3, 4, 5]
第3页数据: [6, 7, 8]
第4页数据: [9]

シナリオ2: 無限シーケンスの生成

例

class FibonacciIterator:
    """フィボナッチ数列イテレータ"""
   
    def __init__(self, max_count=10):
        self.max_count = max_count
        self.count = 0
        self.a, self.b = 0, 1
   
    def __iter__(self):
        return self
   
    def __next__(self):
        if self.count >= self.max_count:
            raise StopIteration
       
        result = self.a
        self.a, self.b = self.b, self.a + self.b
        self.count += 1
        return result

# フィボナッチ数列を生成
fib = FibonacciIterator(8)
print("フィボナッチ数列:", list(fib))

出力結果:

斐波那契数列: [0, 1, 1, 2, 3, 5, 8, 13]

組み込みイテレータツール

Pythonはイテレータを操作するための豊富な組み込み関数を提供しています。

iter()とnext()関数

例

numbers = [1, 2, 3, 4, 5]

# 手動でイテレータを使用
iterator = iter(numbers)

print(next(iterator))  # 出力: 1
print(next(iterator))  # 出力: 2
print(next(iterator))  # 出力: 3

enumerate()関数

例

fruits = ['apple', 'banana', 'orange']

for index, fruit in enumerate(fruits):
    print(f"インデックス {index}: {fruit}")

zip()関数

例

names = ['Alice', 'Bob', 'Charlie']
scores = [85, 92, 78]

for name, score in zip(names, scores):
    print(f"{name} 得点: {score}")

イテレータ vs イテラブル

これら2つの概念の違いを理解することが重要です。

特性 イテラブル (Iterable) イテレータ (Iterator)
定義 実装している__iter__()メソッドを持つオブジェクト 実装している__iter__()と__next__()メソッドを持つオブジェクト
用途 反復可能 実際に反復操作を実行する
状態 通常はステートレス 反復状態(現在位置など)を維持する
例 リスト、タプル、辞書、文字列 iter()返されるオブジェクト

関係図

例

graph TD
A[イテラブル] -->|iterを呼び出し| B[イテレータ]
B -->|nextを繰り返し呼び出し| C[要素を1つずつ]
C --> D[StopIterationまで]

ベストプラクティスとよくある間違い

ベストプラクティス

  1. ジェネレータを使ってコードを簡素化する

例

# 推奨: ジェネレータを使用
def countdown(n):
    while n > 0:
        yield n
        n -= 1

# 非推奨: 手動でイテレータクラスを実装
  1. 組み込み関数を活用する

例

# 推奨
squares = (x*x for x in range(10))  # ジェネレータ式

# 非推奨
class SquareIterator:
    # ... 冗長な実装

よくある間違い

間違い1: イテレータとイテラブルを混同する

例

numbers = [1, 2, 3]

# エラー:リスト自体はイテレータではない
try:
    next(numbers)  # TypeError: 'list' object is not an iterator
except TypeError as e:
    print(f"エラー: {e}")

# 正しい:まずイテレータを取得する
iterator = iter(numbers)
print(next(iterator))  # 出力: 1

エラー 2:イテレータを使い切った後に使用し続ける

例

numbers = [1, 2, 3]
iterator = iter(numbers)

print(list(iterator))  # 出力: [1, 2, 3]
print(list(iterator))  # 出力: [] - イテレータは使い切られました!

実践演習

演習1: カスタムイテレータの作成

次のようなCountdownクラス、指定された数字から 1 までカウントダウンするイテレータを実装します:

例

class Countdown:
    def __init__(self, start):
        self.start = start
   
    def __iter__(self):
        # あなたのコードをここに
        current = self.start
        while current > 0:
            yield current
            current -= 1

# あなたの実装をテストします
for num in Countdown(5):
    print(num)  # 出力されるはず: 5, 4, 3, 2, 1

演習2: ファイル行イテレータ

ファイルを1行ずつ読み取り、各行の先頭に行番号を追加するイテレータを作成します:

例

class NumberedLines:
    def __init__(self, filename):
        self.filename = filename
   
    def __iter__(self):
        with open(self.filename, 'r', encoding='utf-8') as file:
            for line_num, line in enumerate(file, 1):
                yield f"{line_num}: {line.rstrip()}"

まとめ

イテレータパターンは Python プログラミングにおいて極めて重要な概念であり、これにより次のことが可能になります:

  • 統一アクセス:異なるデータ構造を同じ方法で走査する
  • 実装のカプセル化:コレクションの内部構造を隠す
  • 遅延評価:必要なときにだけデータを生成し、メモリを節約する
  • 組み合わせ使用:ジェネレータや内包表記などの機能と完璧に連携する

このシンプルな原則を覚えておきましょう:任意の__iter__()メソッドを実装したオブジェクトは反復可能であり、任意の__next__()メソッドを実装したオブジェクトはイテレータです。

その他の拡張