Python yield文入門|ジェネレータを作る基本
Python yield文の基本的な使い方と実用的な活用法を初心者向けに解説。ジェネレータを作成してメモリ効率の良いプログラムを書く方法を学びましょう。
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)) # 1print(next(gen)) # 2print(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 = 100000list_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,cityAlice,25,TokyoBob,30,OsakaCharlie,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
文を活用してみましょう!