Python yield文入門|ジェネレータを作る基本

Python yield文の基本的な使い方と実用的な活用法を初心者向けに解説。ジェネレータを作成してメモリ効率の良いプログラムを書く方法を学びましょう。

Learning Next 運営
23 分で読めます

Python yield文入門|ジェネレータを作る基本

みなさん、Pythonで大量のデータを扱うときにメモリ不足で困ったことはありませんか?

「リストに全部保存すると重い」 「もっと効率的な方法はないの?」 「yieldって聞いたことはあるけど、よく分からない」

こんな悩みを抱えている方は多いはずです。 でも心配いりません!

この記事では、yield文という便利な機能を初心者の方にも分かりやすく解説します。 基本的な使い方から実用的な活用法まで、段階的に学んでいきましょう。

yield文って何だろう?

yield文は、ジェネレータ関数を作成するためのPythonの機能です。 簡単に言うと、値を一つずつ生成する特別な関数を作る仕組みです。

returnとの違いを見てみよう

# return文を使った通常の関数
def normal_function():
return [1, 2, 3]
# yield文を使ったジェネレータ関数
def generator_function():
yield 1
yield 2
yield 3
# 違いを確認
normal_result = normal_function()
print(normal_result) # [1, 2, 3]
print(type(normal_result)) # <class 'list'>
generator_result = generator_function()
print(generator_result) # <generator object generator_function at 0x...>
print(type(generator_result)) # <class 'generator'>

returnは全ての値を一度に返します。 yieldは値を一つずつ生成するジェネレータオブジェクトを作ります。

イメージとしては、returnは全部の商品を一度に渡すお店。 yieldは注文を受けて一つずつ作って渡してくれるお店という感じです。

基本的な使い方を試してみよう

# 基本的なジェネレータ
def simple_generator():
yield 1
yield 2
yield 3
# ジェネレータオブジェクトの作成
gen = simple_generator()
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3
# forループでも使える
gen2 = simple_generator()
for value in gen2:
print(value) # 1, 2, 3 が順番に出力

next()関数で一つずつ値を取得できます。 forループでも普通のリストのように使えます。

基本的なジェネレータを作ってみよう

様々なパターンでジェネレータを作成してみましょう。

数値のジェネレータ

# 数値を順番に生成
def number_generator(n):
for i in range(n):
yield i
# 使用例
gen = number_generator(5)
for num in gen:
print(num) # 0, 1, 2, 3, 4 が順番に出力
# リストに変換することも可能
numbers = list(number_generator(3))
print(numbers) # [0, 1, 2]

シンプルな数値の生成も、メモリ効率よく行えます。 必要な分だけ生成するので、大きな数でも安心です。

条件付きジェネレータ

# 偶数のみを生成
def even_generator(max_num):
for i in range(0, max_num, 2):
yield i
# 使用例
evens = even_generator(10)
print(list(evens)) # [0, 2, 4, 6, 8]
# より複雑な条件
def filtered_numbers(start, end, condition):
for i in range(start, end):
if condition(i):
yield i
# 3で割り切れる数
divisible_by_3 = filtered_numbers(1, 20, lambda x: x % 3 == 0)
print(list(divisible_by_3)) # [3, 6, 9, 12, 15, 18]

条件に合った値だけを効率的に生成できます。 複雑な条件でも、関数を使って柔軟に対応できます。

文字列のジェネレータ

# 文字列を1文字ずつ生成
def char_generator(text):
for char in text:
yield char
# 使用例
chars = char_generator("Hello")
for char in chars:
print(char) # H, e, l, l, o が順番に出力
# 単語を一つずつ生成
def word_generator(sentence):
words = sentence.split()
for word in words:
yield word.strip('.,!?')
# 使用例
words = word_generator("Hello, world! How are you?")
print(list(words)) # ['Hello', 'world', 'How', 'are', 'you']

文字列の処理でもyieldを活用できます。 長い文章でも、メモリを節約しながら処理できます。

無限ジェネレータの魅力

yieldを使うと、無限に値を生成するジェネレータも作成できます。

無限カウンタ

# 無限にカウントアップ
def infinite_counter(start=0):
count = start
while True:
yield count
count += 1
# 使用例
counter = infinite_counter(10)
for i in range(5): # 最初の5個だけ取得
print(next(counter)) # 10, 11, 12, 13, 14
# 必要な分だけ取り出せる
another_counter = infinite_counter()
first_ten = [next(another_counter) for _ in range(10)]
print(first_ten) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

無限ループでも、必要な分だけ値を取得できます。 全部のデータを事前に作る必要がないので、とても効率的です。

フィボナッチ数列

# フィボナッチ数列を無限に生成
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# 使用例
fib = fibonacci()
for i in range(10):
print(next(fib)) # 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
# 特定の条件まで取得
def fibonacci_under(limit):
fib_gen = fibonacci()
result = []
while True:
value = next(fib_gen)
if value >= limit:
break
result.append(value)
return result
print(fibonacci_under(100)) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

数学的な数列も効率的に生成できます。 無限に計算できるので、どこまでも続く数列を扱えます。

メモリ効率の違いを実感しよう

yieldのメモリ効率の良さを確認してみましょう。

リストとジェネレータの比較

import sys
# リストを使った場合
def create_list(n):
return [i * i for i in range(n)]
# ジェネレータを使った場合
def create_generator(n):
for i in range(n):
yield i * i
# メモリ使用量の比較
n = 100000
list_result = create_list(n)
generator_result = create_generator(n)
print(f"リストのサイズ: {sys.getsizeof(list_result):,} bytes")
print(f"ジェネレータのサイズ: {sys.getsizeof(generator_result):,} bytes")
# 例: リストのサイズ: 900,112 bytes
# ジェネレータのサイズ: 112 bytes

この例では、ジェネレータの方が圧倒的に小さいメモリ使用量になります。 大量のデータを扱うときに、この差はとても重要です。

実際の処理での効率性

# 大量のデータを効率的に処理
def process_large_data(size):
for i in range(size):
# 複雑な計算処理
result = i * i + i + 1
yield result
# 使用例
data_gen = process_large_data(1000000)
# 最初の10個だけ処理
first_ten = []
for i, value in enumerate(data_gen):
if i >= 10:
break
first_ten.append(value)
print(first_ten) # [1, 3, 7, 13, 21, 31, 43, 57, 73, 91]

大量のデータでも、必要な分だけ処理できます。 全部を一度に計算する必要がないので、プログラムが軽快に動きます。

ファイル処理での活用

ファイル処理でもyieldが威力を発揮します。

大きなファイルの読み込み

# 大きなファイルを1行ずつ読み込み
def read_large_file(filename):
try:
with open(filename, 'r', encoding='utf-8') as file:
for line in file:
yield line.strip()
except FileNotFoundError:
print(f"ファイル '{filename}' が見つかりません")
return
# 使用例(実際のファイルがある場合)
# file_lines = read_large_file('large_file.txt')
# for i, line in enumerate(file_lines):
# if i >= 100: # 最初の100行だけ処理
# break
# print(f"行{i+1}: {line}")

大きなファイルでも、メモリを大量に使わずに処理できます。 一行ずつ処理するので、ギガバイト級のファイルでも安心です。

CSVファイルの効率的な処理

# CSVファイルを1行ずつ処理
def process_csv_file(filename):
try:
with open(filename, 'r', encoding='utf-8') as file:
header = next(file).strip().split(',')
for line in file:
values = line.strip().split(',')
yield dict(zip(header, values))
except FileNotFoundError:
print(f"ファイル '{filename}' が見つかりません")
return
# 模擬的な例(文字列として)
def process_csv_data(csv_text):
lines = csv_text.strip().split('
')
header = lines[0].split(',')
for line in lines[1:]:
values = line.split(',')
yield dict(zip(header, values))
# 使用例
csv_data = """name,age,city
Alice,25,Tokyo
Bob,30,Osaka
Charlie,35,Kyoto"""
csv_gen = process_csv_data(csv_data)
for row in csv_gen:
print(f"{row['name']}さん({row['age']}歳、{row['city']})")

CSVファイルの処理も効率的に行えます。 大量のデータでも、一行ずつ処理するので安心です。

実践的な活用例

実際のプログラミングでよく使われる例を見てみましょう。

ページネーション機能

# ページネーション機能
def paginate_data(data, page_size):
for i in range(0, len(data), page_size):
yield data[i:i + page_size]
# 使用例
large_data = list(range(1, 101)) # 1-100の数値
pages = paginate_data(large_data, 10)
for page_num, page_data in enumerate(pages, 1):
print(f"ページ {page_num}: {page_data[:3]}...{page_data[-3:]}")
# ページ 1: [1, 2, 3]...[8, 9, 10]
# ページ 2: [11, 12, 13]...[18, 19, 20]
# ...

大量のデータを効率的にページ分けできます。 Webアプリケーションの検索結果表示などでよく使われます。

バッチ処理システム

# バッチ処理用のジェネレータ
def batch_processor(data, batch_size):
batch = []
for item in data:
batch.append(item)
if len(batch) >= batch_size:
yield batch
batch = []
# 残りのデータがある場合
if batch:
yield batch
# 使用例
data = range(1, 23) # 1-22の数値
batches = batch_processor(data, 5)
for batch_num, batch in enumerate(batches, 1):
print(f"バッチ {batch_num}: {list(batch)}")
# バッチ 1: [1, 2, 3, 4, 5]
# バッチ 2: [6, 7, 8, 9, 10]
# ...

データを効率的にバッチ処理できます。 機械学習のデータ処理やAPIの一括処理でよく使われる手法です。

データストリーミング処理

# データストリーミング処理
def data_stream_processor(data_source):
for raw_data in data_source:
# データの前処理
if isinstance(raw_data, str):
processed = raw_data.strip().upper()
else:
processed = str(raw_data)
# 条件に合うデータのみ出力
if processed and len(processed) > 2:
yield processed
# 使用例
raw_data = ["hello", " world ", "py", "", "python", 123, "AI"]
stream = data_stream_processor(raw_data)
for processed_data in stream:
print(f"処理済み: {processed_data}")
# 処理済み: HELLO
# 処理済み: WORLD
# 処理済み: PYTHON
# 処理済み: 123

リアルタイムデータ処理にも活用できます。 センサーデータやログファイルの監視などで重宝します。

yield fromで更に便利に

yield fromを使うと、他のジェネレータを組み合わせることができます。

複数のジェネレータの組み合わせ

# 複数のジェネレータを組み合わせ
def generator1():
yield 1
yield 2
def generator2():
yield 3
yield 4
def combined_generator():
yield from generator1()
yield from generator2()
# 使用例
combined = combined_generator()
print(list(combined)) # [1, 2, 3, 4]
# より実用的な例
def even_numbers(max_num):
for i in range(0, max_num, 2):
yield i
def odd_numbers(max_num):
for i in range(1, max_num, 2):
yield i
def all_numbers(max_num):
yield from even_numbers(max_num)
yield from odd_numbers(max_num)
print(list(all_numbers(10))) # [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]

複数のジェネレータを簡単に組み合わせられます。 処理を分割して管理しやすくできます。

再帰的なジェネレータ

# 再帰的なジェネレータ
def flatten_list(nested_list):
for item in nested_list:
if isinstance(item, list):
yield from flatten_list(item)
else:
yield item
# 使用例
nested = [1, [2, 3], [4, [5, 6]], 7]
flattened = flatten_list(nested)
print(list(flattened)) # [1, 2, 3, 4, 5, 6, 7]
# より複雑な例
complex_nested = [1, [2, [3, [4, 5]]], 6, [7, 8]]
print(list(flatten_list(complex_nested))) # [1, 2, 3, 4, 5, 6, 7, 8]

再帰的な処理でもyield fromが役立ちます。 ネストした構造を平坦化する処理などで威力を発揮します。

注意点とベストプラクティス

yieldを使用する際の注意点を確認しましょう。

ジェネレータの一回限りの使用

# ジェネレータは一回限り
def simple_gen():
yield 1
yield 2
yield 3
gen = simple_gen()
print(list(gen)) # [1, 2, 3]
print(list(gen)) # [] (空のリスト)
# 再度使用したい場合は新しいジェネレータを作成
gen2 = simple_gen()
print(list(gen2)) # [1, 2, 3]
# 何度も使いたい場合の対策
def reusable_generator_factory():
def inner_generator():
yield 1
yield 2
yield 3
return inner_generator
gen_factory = reusable_generator_factory()
print(list(gen_factory())) # [1, 2, 3]
print(list(gen_factory())) # [1, 2, 3]

ジェネレータは一度使い切ると、再利用できません。 同じ処理を何度も行いたい場合は、新しいジェネレータを作成しましょう。

エラーハンドリング

# エラーハンドリング付きのジェネレータ
def safe_generator(data):
for item in data:
try:
# 何らかの処理
result = int(item) * 2
yield result
except ValueError:
# エラーが発生した場合はスキップ
print(f"'{item}' は数値に変換できません(スキップ)")
continue
# 使用例
mixed_data = ["1", "2", "abc", "4", "def", "6"]
safe_gen = safe_generator(mixed_data)
print(list(safe_gen)) # [2, 4, 8, 12]

エラーが発生する可能性がある処理では、適切なエラーハンドリングを行いましょう。 処理を続行するか停止するかを適切に判断することが重要です。

まとめ:yield文をマスターしよう

yield文は、Pythonでメモリ効率の良いジェネレータを作成するための重要な機能です。

今回学んだ重要なポイント

yield文で覚えておきたいことです:

  • 基本概念: 値を一つずつ生成する特別な関数を作る
  • メモリ効率: 大量のデータでもメモリ使用量を抑制
  • 無限ジェネレータ: 必要な分だけ値を生成可能
  • ファイル処理: 大きなファイルを効率的に処理
  • yield from: 複数のジェネレータを組み合わせ

yield文が活躍する場面

日常的な開発でよく使われる場面です:

  • 大量データ処理: CSVファイルやログファイルの解析
  • ストリーミング: リアルタイムデータの処理
  • バッチ処理: データを小分けにして処理
  • 無限数列: 数学的な計算や乱数生成

使い方のコツ

効率的にyield文を使うためのポイントです:

  • 一回限り: ジェネレータは使い切りなので注意
  • エラー対策: 適切な例外処理を忘れずに
  • 組み合わせ: yield fromで処理を分割・統合
  • 条件判定: 必要な値だけを生成する工夫

大量のデータを扱うプログラムでは、yieldを使うことでメモリ効率を大幅に改善できます。

まずは簡単な数値の生成から始めて、徐々に複雑な処理に応用してみてください。 メモリ効率を考慮したプログラムを書くために、ぜひyield文を活用してみましょう!

関連記事