Python イテレータパターン
想像してみてください。色とりどりのキャンディが詰まった瓶があります。一粒ずつ味わいたいが、一度に全部のキャンディを取り出したくはありません。そんなとき、瓶に手を伸ばして一粒だけ取り出すでしょう。このプロセスが反復(イテレーション)です。
プログラミングにおいて、イテレータパターンはデザインパターンの一つで、集合オブジェクトの内部表現を公開することなく、その要素に順番にアクセスする方法を提供します。
中核概念
イテレータパターンは2つの主要な構成要素を含みます。
- イテレータ (Iterator):要素へのアクセスと走査を定義するインターフェースを担当します
- イテラブル (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: クラスを使用してイテレータを実装する
カスタムの書籍イテレータを作成しましょう。
例
"""書籍イテレータクラス"""
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はより簡潔な方法、ジェネレータ関数を提供しています。
例
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: ページングデータの読み取り
例
"""ページングデータイテレータのシミュレーション"""
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: 無限シーケンスの生成
例
"""フィボナッチ数列イテレータ"""
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()関数
例
# 手動でイテレータを使用
iterator = iter(numbers)
print(next(iterator)) # 出力: 1
print(next(iterator)) # 出力: 2
print(next(iterator)) # 出力: 3
enumerate()関数
例
for index, fruit in enumerate(fruits):
print(f"インデックス {index}: {fruit}")
zip()関数
例
scores = [85, 92, 78]
for name, score in zip(names, scores):
print(f"{name} 得点: {score}")
イテレータ vs イテラブル
これら2つの概念の違いを理解することが重要です。
| 特性 | イテラブル (Iterable) | イテレータ (Iterator) |
|---|---|---|
| 定義 | 実装している__iter__()メソッドを持つオブジェクト |
実装している__iter__()と__next__()メソッドを持つオブジェクト |
| 用途 | 反復可能 | 実際に反復操作を実行する |
| 状態 | 通常はステートレス | 反復状態(現在位置など)を維持する |
| 例 | リスト、タプル、辞書、文字列 | iter()返されるオブジェクト |
関係図
例
A[イテラブル] -->|iterを呼び出し| B[イテレータ]
B -->|nextを繰り返し呼び出し| C[要素を1つずつ]
C --> D[StopIterationまで]
ベストプラクティスとよくある間違い
ベストプラクティス
- ジェネレータを使ってコードを簡素化する
例
def countdown(n):
while n > 0:
yield n
n -= 1
# 非推奨: 手動でイテレータクラスを実装
- 組み込み関数を活用する
例
squares = (x*x for x in range(10)) # ジェネレータ式
# 非推奨
class SquareIterator:
# ... 冗長な実装
よくある間違い
間違い1: イテレータとイテラブルを混同する
例
# エラー:リスト自体はイテレータではない
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:イテレータを使い切った後に使用し続ける
例
iterator = iter(numbers)
print(list(iterator)) # 出力: [1, 2, 3]
print(list(iterator)) # 出力: [] - イテレータは使い切られました!
実践演習
演習1: カスタムイテレータの作成
次のようなCountdownクラス、指定された数字から 1 までカウントダウンするイテレータを実装します:
例
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行ずつ読み取り、各行の先頭に行番号を追加するイテレータを作成します:
例
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__()メソッドを実装したオブジェクトはイテレータです。