Pythonイテレータとは?繰り返し処理の基礎概念

Pythonイテレータの基本概念を初心者向けに解説。イテラブル、イテレータの違い、カスタムイテレータの作成、ジェネレータとの関係まで、実例とともに詳しく紹介します。

Learning Next 運営
28 分で読めます

みなさん、Pythonでfor文を使って繰り返し処理をする時に「なぜリストの中身を順番に取り出せるんだろう?」と疑問に思ったことはありませんか?

「for文の仕組みをもっと詳しく知りたい」 「イテレータって言葉を聞くけど、よく分からない」 「自分でも繰り返し処理を作ってみたい」

そんな疑問を抱えている方も多いはず。 でも大丈夫です!

この記事では、Pythonのイテレータの基本概念を初心者向けに分かりやすく解説します。 最初は難しく感じるかもしれませんが、一緒に学んでいけばきっと理解できるようになりますよ!

イテレータって何?

イテレータとは、データの集合を順番に取り出すための仕組みのことです。 簡単に言うと、「データを一つずつ順番に渡してくれる係の人」のようなものですね。

for文の魔法の仕組み

普段何気なく使っているfor文では、内部でイテレータが働いています。 その仕組みを見てみましょう:

# いつものfor文
fruits = ["りんご", "バナナ", "オレンジ"]
for fruit in fruits:
print(fruit)

実行結果:

りんご バナナ オレンジ

実は、この裏では次のような処理が行われています:

# for文の裏側で起こっていること
fruits = ["りんご", "バナナ", "オレンジ"]
iterator = iter(fruits) # イテレータを作成
try:
while True:
fruit = next(iterator) # 次の要素を取得
print(fruit)
except StopIteration:
pass # 要素がなくなったら終了

実行結果:

りんご バナナ オレンジ

for文は、このような複雑な処理を自動的にやってくれているんです。 便利ですよね!

イテレータの2つの重要な役割

イテレータには、2つの重要なメソッドがあります:

# イテレータクラスの基本構造
class SimpleIterator:
def __init__(self, data):
self.data = data
self.index = 0
def __iter__(self):
# イテレータ自身を返す
return self
def __next__(self):
# 次の要素を返す
if self.index < len(self.data):
result = self.data[self.index]
self.index += 1
return result
else:
# 要素がなくなったらStopIterationを発生
raise StopIteration

使ってみましょう:

# 自作イテレータを使ってみる
numbers = [1, 2, 3, 4, 5]
my_iterator = SimpleIterator(numbers)
print("自作イテレータで繰り返し処理:")
for num in my_iterator:
print(num)

実行結果:

自作イテレータで繰り返し処理: 1 2 3 4 5

__iter__()で「繰り返しができる準備をする」、__next__()で「次の要素を取り出す」という役割分担になっています。

イテラブルとイテレータの違い

この2つの違いは、プログラミング初心者が混乱しがちなポイントです。 でも大丈夫、分かりやすく説明しますね。

イテラブルオブジェクト

イテラブル(iterable)は、繰り返し処理が可能なオブジェクトのことです。 Pythonにはたくさんのイテラブルがあります:

# いろんなイテラブルオブジェクト
my_list = [1, 2, 3] # リスト
my_tuple = (1, 2, 3) # タプル
my_string = "abc" # 文字列
my_dict = {"a": 1, "b": 2} # 辞書
my_set = {1, 2, 3} # 集合
# どれもfor文で繰り返せる
print("リストの要素:")
for item in my_list:
print(item)
print("
文字列の文字:")
for char in my_string:
print(char)
print("
辞書のキー:")
for key in my_dict:
print(f"{key}: {my_dict[key]}")

実行結果:

リストの要素: 1 2 3 文字列の文字: a b c 辞書のキー: a: 1 b: 2

これらは全て「イテラブル」と呼ばれるオブジェクトです。

イテレータの作り方

iter()関数を使って、イテラブルからイテレータを作ることができます:

# イテラブルからイテレータを作成
numbers = [1, 2, 3, 4, 5]
iterator = iter(numbers)
print(f"元のリスト: {type(numbers)}") # <class 'list'>
print(f"イテレータ: {type(iterator)}") # <class 'list_iterator'>
# next()関数で要素を一つずつ取得
print("
next()で要素を取得:")
print(next(iterator)) # 1
print(next(iterator)) # 2
print(next(iterator)) # 3
# 残りの要素をfor文で取得
print("
残りの要素:")
for num in iterator:
print(num) # 4, 5

実行結果:

元のリスト: <class 'list'> イテレータ: <class 'list_iterator'> next()で要素を取得: 1 2 3 残りの要素: 4 5

イテレータは一度使うと終わってしまうことに注意してください。

大切な違いを理解しよう

両者の重要な違いを確認してみましょう:

# イテラブルは何度でも使える
fruits = ["りんご", "バナナ", "オレンジ"]
print("1回目の繰り返し:")
for fruit in fruits:
print(fruit)
print("
2回目の繰り返し:")
for fruit in fruits:
print(fruit) # 同じ結果が出力される
# イテレータは一度きり
iterator = iter(fruits)
print("イテレータ1回目:")
for fruit in iterator:
print(fruit)
print("
イテレータ2回目:")
for fruit in iterator:
print(fruit) # 何も出力されない

実行結果:

1回目の繰り返し: りんご バナナ オレンジ 2回目の繰り返し: りんご バナナ オレンジ イテレータ1回目: りんご バナナ オレンジ イテレータ2回目: (何も出力されない)

イテラブルは何度でも使えるが、イテレータは一度きりということを覚えておきましょう。

組み込み関数とイテレータ

Pythonには、イテレータと組み合わせて使える便利な関数がたくさんあります。 これらを上手に使うと、プログラムがもっと効率的になりますよ。

iter()関数の便利な使い方

iter()関数には、基本的な使い方以外にも便利な機能があります:

# 基本的な使い方
numbers = [1, 2, 3, 4, 5]
iterator = iter(numbers)
print("基本的なイテレータ:")
for num in iterator:
print(num)
# 関数とセンチネル値を指定した使い方
import random
def roll_dice():
return random.randint(1, 6)
# 6が出るまで繰り返すイテレータ
dice_iterator = iter(roll_dice, 6)
print("
サイコロゲーム(6が出るまで):")
for result in dice_iterator:
print(f"サイコロの目: {result}")
print("6が出たので終了!")

2つ目の引数(センチネル値)を指定すると、その値が返されるまで関数を繰り返し実行します。

next()関数とデフォルト値

next()関数では、エラーを防ぐためのデフォルト値を指定できます:

# デフォルト値の使い方
numbers = [1, 2, 3]
iterator = iter(numbers)
print("要素を順番に取得:")
print(next(iterator)) # 1
print(next(iterator)) # 2
print(next(iterator)) # 3
# デフォルト値を指定すればエラーにならない
print(next(iterator, "もう要素がありません")) # デフォルト値が返される
# デフォルト値なしだとStopIteration
try:
print(next(iterator))
except StopIteration:
print("StopIterationエラーが発生しました")

実行結果:

要素を順番に取得: 1 2 3 もう要素がありません StopIterationエラーが発生しました

デフォルト値を指定することで、安全にイテレータを使うことができます。

enumerate()とzip()の活用

これらの関数もイテレータを返す便利な機能です:

# enumerate() - インデックスと値のペア
fruits = ["りんご", "バナナ", "オレンジ"]
enum_iterator = enumerate(fruits)
print(f"enumerate()の型: {type(enum_iterator)}")
print("
番号付きリスト:")
for index, fruit in enum_iterator:
print(f"{index + 1}. {fruit}")
# zip() - 複数のイテラブルをまとめる
names = ["田中", "佐藤", "鈴木"]
ages = [25, 30, 35]
cities = ["東京", "大阪", "名古屋"]
zip_iterator = zip(names, ages, cities)
print(f"
zip()の型: {type(zip_iterator)}")
print("
個人情報:")
for name, age, city in zip_iterator:
print(f"{name}さん({age}歳)は{city}在住")

実行結果:

enumerate()の型: <class 'enumerate'> 番号付きリスト: 1. りんご 2. バナナ 3. オレンジ zip()の型: <class 'zip'> 個人情報: 田中さん(25歳)は東京在住 佐藤さん(30歳)は大阪在住 鈴木さん(35歳)は名古屋在住

これらの関数は、メモリ効率の良いイテレータを返してくれます。

カスタムイテレータを作ってみよう

自分だけのイテレータを作ることで、イテレータの仕組みがより深く理解できます。 いくつかの例を一緒に作ってみましょう。

カウントダウンイテレータ

指定した数からカウントダウンするイテレータを作ってみます:

class CountDown:
"""指定した数からカウントダウンするイテレータ"""
def __init__(self, start):
self.start = start
self.current = start
def __iter__(self):
# 新しいインスタンスを返すことで何度でも使える
return CountDown(self.start)
def __next__(self):
if self.current <= 0:
raise StopIteration
result = self.current
self.current -= 1
return result

使ってみましょう:

# カウントダウンイテレータの使用
countdown = CountDown(5)
print("カウントダウン開始:")
for num in countdown:
print(f"{num}...")
print("発射!")
print("
再度カウントダウン:")
for num in countdown:
print(f"{num}...")
print("また発射!")

実行結果:

カウントダウン開始: 5... 4... 3... 2... 1... 発射! 再度カウントダウン: 5... 4... 3... 2... 1... また発射!

何度でも使えるイテレータが作れました!

偶数だけ返すイテレータ

特定の条件を満たす数値だけを返すイテレータです:

class EvenNumbers:
"""偶数のみを返すイテレータ"""
def __init__(self, max_value):
self.max_value = max_value
self.current = 0
def __iter__(self):
return self
def __next__(self):
while self.current <= self.max_value:
if self.current % 2 == 0:
result = self.current
self.current += 1
return result
self.current += 1
raise StopIteration

使用例:

# 偶数イテレータの使用
even_iter = EvenNumbers(10)
print("10以下の偶数:")
for num in even_iter:
print(num)

実行結果:

10以下の偶数: 0 2 4 6 8 10

条件に合う値だけを効率的に取り出せます。

フィルタ機能付きイテレータ

もっと汎用的なフィルタ機能を持つイテレータを作ってみましょう:

class FilteredIterator:
"""条件を満たす要素のみを返すイテレータ"""
def __init__(self, iterable, condition):
self.iterator = iter(iterable)
self.condition = condition
def __iter__(self):
return self
def __next__(self):
while True:
try:
item = next(self.iterator)
if self.condition(item):
return item
except StopIteration:
raise

使用例:

# フィルタイテレータの使用
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 奇数のみを取り出す
odd_numbers = FilteredIterator(numbers, lambda x: x % 2 == 1)
print("奇数のみ:")
for num in odd_numbers:
print(num)
# 5より大きい数値のみを取り出す
big_numbers = FilteredIterator(numbers, lambda x: x > 5)
print("
5より大きい数値:")
for num in big_numbers:
print(num)

実行結果:

奇数のみ: 1 3 5 7 9 5より大きい数値: 6 7 8 9 10

このように、条件を自由に設定できる汎用的なイテレータも作れます。

ジェネレータという簡単な方法

イテレータを作る別の方法として、ジェネレータという仕組みがあります。 これを使うと、クラスを書かなくても簡単にイテレータが作れるんです。

ジェネレータ関数

yield文を使ったジェネレータ関数を作ってみましょう:

def countdown_generator(start):
"""ジェネレータ関数によるカウントダウン"""
current = start
while current > 0:
yield current
current -= 1

使ってみます:

# ジェネレータの使用
gen = countdown_generator(5)
print(f"ジェネレータの型: {type(gen)}")
print("
カウントダウン:")
for num in gen:
print(f"{num}...")
print("終了!")

実行結果:

ジェネレータの型: <class 'generator'> カウントダウン: 5... 4... 3... 2... 1... 終了!

クラスを書くよりもずっと簡単ですね!

フィボナッチ数列ジェネレータ

もう少し複雑な例として、フィボナッチ数列を作ってみましょう:

def fibonacci_generator(max_count):
"""フィボナッチ数列ジェネレータ"""
a, b = 0, 1
count = 0
while count < max_count:
yield a
a, b = b, a + b
count += 1
# フィボナッチ数列の表示
print("フィボナッチ数列(最初の10項):")
for num in fibonacci_generator(10):
print(num)

実行結果:

フィボナッチ数列(最初の10項): 0 1 1 2 3 5 8 13 21 34

数学的なシーケンスも簡単に作れます。

ジェネレータ式

リスト内包表記に似た構文で、ジェネレータを作ることもできます:

# リスト内包表記(全要素をメモリに保存)
squares_list = [x**2 for x in range(10)]
print(f"リスト: {squares_list}")
print(f"メモリ使用量(概算): {squares_list.__sizeof__()} bytes")
# ジェネレータ式(必要時に要素を生成)
squares_gen = (x**2 for x in range(10))
print(f"
ジェネレータ: {squares_gen}")
print(f"メモリ使用量(概算): {squares_gen.__sizeof__()} bytes")
# ジェネレータから値を取得
print("
ジェネレータから値を取得:")
for square in squares_gen:
print(square)

実行結果:

リスト: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] メモリ使用量(概算): 184 bytes ジェネレータ: <generator object <genexpr> at 0x...> メモリ使用量(概算): 112 bytes ジェネレータから値を取得: 0 1 4 9 16 25 36 49 64 81

ジェネレータは、メモリ効率が良いのが大きなメリットです。

ジェネレータの状態保持

ジェネレータは実行状態を記憶してくれます:

def stateful_generator():
"""状態を保持するジェネレータ"""
print("ジェネレータ開始")
for i in range(3):
print(f"yield前: {i}")
yield i
print(f"yield後: {i}")
print("ジェネレータ終了")
# 状態保持の確認
gen = stateful_generator()
print("1回目のnext():")
print(next(gen))
print("
2回目のnext():")
print(next(gen))
print("
残りをfor文で処理:")
for value in gen:
print(f"値: {value}")

実行結果:

1回目のnext(): ジェネレータ開始 yield前: 0 0 2回目のnext(): yield後: 0 yield前: 1 1 残りをfor文で処理: yield後: 1 yield前: 2 値: 2 yield後: 2 ジェネレータ終了

yield文で実行を一時停止し、次の呼び出しで続きから再開している様子が分かりますね。

実践的な活用例

イテレータを実際のプロジェクトでどのように使うか、具体例を見てみましょう。 これらの例を参考に、実際の開発で活用してみてください。

ファイル読み込みイテレータ

大きなファイルを効率的に処理するイテレータです:

def file_lines_generator(filename, encoding='utf-8'):
"""ファイルを1行ずつ読み込むジェネレータ"""
try:
with open(filename, 'r', encoding=encoding) as f:
for line in f:
yield line.rstrip('
\r')
except FileNotFoundError:
print(f"ファイルが見つかりません: {filename}")
return
# サンプルファイルの作成(デモ用)
sample_content = """1行目: こんにちは
2行目: Pythonの
3行目: イテレータは
4行目: とても便利です"""
with open("sample.txt", "w", encoding="utf-8") as f:
f.write(sample_content)
# ファイル読み込みの使用例
print("ファイル内容:")
for line_num, line in enumerate(file_lines_generator("sample.txt"), 1):
print(f"{line_num}: {line}")

実行結果:

ファイル内容: 1: 1行目: こんにちは 2: 2行目: Pythonの 3: 3行目: イテレータは 4: 4行目: とても便利です

大きなファイルでも、メモリを節約しながら処理できます。

データ変換パイプライン

複数のジェネレータを組み合わせたデータ処理パイプラインです:

def data_source():
"""データソースジェネレータ"""
data = [
{"name": "田中", "age": 25, "salary": 300000},
{"name": "佐藤", "age": 35, "salary": 450000},
{"name": "鈴木", "age": 28, "salary": 380000},
{"name": "高橋", "age": 42, "salary": 520000},
{"name": "伊藤", "age": 31, "salary": 410000}
]
for item in data:
yield item
def filter_by_age(data_iter, min_age):
"""年齢でフィルタリング"""
for item in data_iter:
if item["age"] >= min_age:
yield item
def add_tax_calculation(data_iter, tax_rate=0.2):
"""税金計算を追加"""
for item in data_iter:
item = item.copy() # 元データを変更しない
item["tax"] = int(item["salary"] * tax_rate)
item["net_salary"] = item["salary"] - item["tax"]
yield item
def format_output(data_iter):
"""出力形式を整える"""
for item in data_iter:
yield f"{item['name']}さん({item['age']}歳): 手取り{item['net_salary']:,}円"

パイプラインを実行してみます:

# パイプライン実行
print("30歳以上の従業員の手取り給与:")
pipeline = data_source()
pipeline = filter_by_age(pipeline, 30)
pipeline = add_tax_calculation(pipeline, 0.2)
pipeline = format_output(pipeline)
for result in pipeline:
print(result)

実行結果:

30歳以上の従業員の手取り給与: 佐藤さん(35歳): 手取り360,000円 高橋さん(42歳): 手取り416,000円 伊藤さん(31歳): 手取り328,000円

各段階でデータを変換しながら、効率的に処理できています。

バッチ処理イテレータ

大量のデータを小さなバッチに分割して処理するイテレータです:

def batch_iterator(iterable, batch_size):
"""イテラブルを指定サイズのバッチに分割"""
iterator = iter(iterable)
while True:
batch = []
try:
for _ in range(batch_size):
batch.append(next(iterator))
except StopIteration:
if batch:
yield batch
break
yield batch

使用例:

# バッチ処理の例
data = list(range(1, 23)) # 1から22までの数値
print(f"元データ: {data}")
print("
5個ずつのバッチに分割:")
for batch_num, batch in enumerate(batch_iterator(data, 5), 1):
print(f"バッチ {batch_num}: {batch}")
# ここで各バッチを処理(例:データベースに保存など)

実行結果:

元データ: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22] 5個ずつのバッチに分割: バッチ 1: [1, 2, 3, 4, 5] バッチ 2: [6, 7, 8, 9, 10] バッチ 3: [11, 12, 13, 14, 15] バッチ 4: [16, 17, 18, 19, 20] バッチ 5: [21, 22]

大量のデータを効率的に処理できる仕組みです。

まとめ

Pythonイテレータの基本概念から実践的な活用方法まで学んできました。 重要なポイントをまとめますね。

基本概念

  • イテレータは順番にデータを取り出す仕組み
  • イテラブルは繰り返し処理ができるオブジェクト
  • for文の内部でイテレータが活用されている
  • __iter__()__next__()メソッドが重要

実践的な知識

  • ジェネレータでクラスより簡単にイテレータを作成
  • yield文で状態を保持しながら値を返す
  • メモリ効率的なデータ処理が可能
  • パイプライン処理でデータ変換を効率化

重要なメリット

  • 大量データを効率的に処理
  • メモリ使用量を大幅に削減
  • 無限シーケンスの表現が可能
  • コードの再利用性が向上

イテレータは、Pythonプログラミングの重要な概念の一つです。 最初は複雑に感じるかもしれませんが、理解すれば大量のデータ処理や効率的なプログラム作成ができるようになります。

ぜひ実際にコードを書いて実行しながら、イテレータの仕組みを体験してみてください! きっとプログラミングの幅が広がるはずです。

関連記事