IT・WEB・ゲーム業界の転職に強いR-Stone

転職コラム

Pythonのyieldとは?returnとの違いや使い方をわかりやすく解説

大量のデータを処理する際にメモリ不足で悩んだ経験はありませんか。

Pythonのyieldキーワードを使えば、メモリを大量消費せずに大容量データを処理できるジェネレーター関数を作成できます。関数の実行を一時停止する動作により、必要な分だけ値を処理・生成できます。

本記事では、yieldの基本概念から実際の開発現場での活用方法まで、豊富なコード例とともに初心者にも分かりやすく詳しく解説します。

Pythonのyieldとは?

yieldは関数をジェネレーター関数に変換するキーワードです。コードが複雑になりがちなイテレーターを簡単に実装できます。

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

ジェネレーター関数を使うと、メモリ効率に優れた方法で複数回に分けて値を生成できます。

returnとの違い

returnは関数を終了して値を返しますが、yieldは実行を一時停止して値を返します。

# return を使った関数

def return_function():

    return 1

    return 2  # この行は実行されない

 

# yield を使った関数

def yield_function():

    yield 1

    yield 2  # この行も実行される

yieldを使った関数は複数回呼び出して複数の値を順次返せます。

ジェネレーターとイテレーターの関係

ジェネレーターはイテレーターの一種です。

イテレーターとは、range()関数やlist()関数のようにfor文やnext()関数で順次値を取得できるオブジェクトです。

class MyRange:  # range()と似たイテレーターの定義

    def __init__(self, stop):

        self.start = 0

        self.stop = stop

        self.current = self.start

 

    def __iter__(self):

        return self

 

    def __next__(self):

        if self.current >= self.stop:

            raise StopIteration  # 終了条件

        value = self.current

        self.current += 1  # 1ずつ増加

        return value

 

# MyRangeイテレーターの使用例

for number in MyRange(5):  # 0から4まで

    print(number)

ジェネレーター関数を使うと、簡単にイテレーターを定義できます。

def my_range(stop):  # range()と似たジェネレーター関数の定義

    current = 0

    while current < stop:

        yield current  # 値を生成

        current += 1  # 1ずつ増加

 

# ジェネレーターオブジェクトを作成

counter = my_range(3)

 

# next()で値を取得

print(next(counter))  # 0

print(next(counter))  # 1

print(next(counter))  # 2

yieldの基本的な使い方

ジェネレーター関数はdefキーワードで定義し、関数内でyieldを使用します。関数を呼び出すとジェネレーターオブジェクトが返され、next()関数で順次値を取得できます。

def simple_generator():

    yield 1

    yield 2

    yield 3

 

gen = simple_generator()  # ジェネレーターオブジェクトを作成

print(next(gen))  # 1

print(next(gen))  # 2

print(next(gen))  # 3

連番ジェネレーターを作る

yieldを使用すれば、連番を生成するジェネレーター関数を作成できます。

# startで開始しstepの増加分で無限に数を生成するジェネレーター関数

def count_up(start=0, step=1):

    current = start

    while True# 無限ループで連番を生成

        yield current

        current += step

 

# 使用例

counter = count_up(1, 2# 1から始まり2ずつ増加

print(next(counter))  # 1

print(next(counter))  # 3

print(next(counter))  # 5

for文で使う

ジェネレーターは、yieldする値が尽きた場合も適切に処理するため、for文で直接使用できます。

def countdown(n):

    while n > 0:

        yield n

        n -= 1

 

# for文での使用

for num in countdown(3):

    print(num)  # 3, 2, 1の順で出力

ジェネレーター内包表記

ジェネレーター内包表記もリスト内包表記のように書けます。ただし、()で囲みます。

# リスト内包表記

numbers = [x for x in range(1000000)]  # メモリを大量消費

# ジェネレーター内包表記

gen_numbers = (x for x in range(1000000))  # メモリ効率がよい

Pythonでyieldを使うメリット

Pythonのyieldを使うと、メモリ効率とパフォーマンスの両面で大きな利点があります。

大量のデータや無限シーケンスを扱える

例えば、100万個の数値を格納するリストは相当なメモリを消費しますが、yieldを使うと現在の状態だけ保持すればよく、省メモリです。

# リスト:すべての値をメモリに保存

def create_list(n):

    return [i for i in range(n)]

 

# ジェネレーター:必要な分だけ生成

def create_generator(n):

    for i in range(n):

        yield i

 

# 無限フィボナッチ数列の例

def fibonacci():

    a, b = 0, 1

    while True:

        yield a

        a, b = b, a + b

遅延評価(必要なときだけ計算)が可能

値が実際に要求されるまで計算を実行しないため、無駄な処理を避けられます。

import time

# 計算コストが高い関数

def expensive_calculation(x):

    print(f”計算中: {x}# 実際に呼ばれた場合だけ表示

    time.sleep(1# 時間のかかる何らかの処理

    return x * x

 

# 上記を使うジェネレーター関数

def generate_squares(numbers):

    for num in numbers:

        yield expensive_calculation(num)

 

# ジェネレーターを作成(この時点では計算されない)

squares = generate_squares([1, 2, 3, 4, 5])

 

# 最初の2つだけ取得(3つ目以降は計算されない)

for i, square in enumerate(squares):

    print(square)

    if i >= 1:

        break

計算コストが高い処理や条件によって早期終了する可能性がある処理で、威力を発揮します。

yieldの実践的な使用例

実際の開発現場でよく使われるyieldの活用例を見ていきましょう。

大容量ファイルの行ごとの読み込み・処理

yieldを使えば、数GBのファイルでも、必要な分だけメモリを使って効率的に処理できます。

def read_large_file(file_path):

    with open(file_path, ‘r’) as file:

        for line in file:

            yield line.strip()  # 1行ずつ返す

 

# 使用例

for line in read_large_file(‘large_log.txt’):

    if ‘ERROR’ in line:

        print(line)

複数のジェネレーターを連結してデータを順次加工

yieldを使って段階的にデータを変換できます。

def read_numbers():

    for i in range(1, 11):

        yield i

 

def square_numbers(numbers):

    for num in numbers:

        yield num ** 2  # 2乗する

 

def filter_even(numbers):

    for num in numbers:

        if num % 2 == 0:

            yield num  # 偶数のみ返す

 

# パイプラインでデータを処理

pipeline = filter_even(square_numbers(read_numbers()))

result = list(pipeline)  # [4, 16, 36, 64, 100]

yield fromによるサブジェネレータ呼び出し

yield fromを使うと、他のジェネレーターの値をまとめて返せます。

def read_file1():

    yield “ファイル1の行1”

    yield “ファイル1の行2”

 

def read_file2():

    yield “ファイル2の行1”

    yield “ファイル2の行2”

 

# 複数のデータを統合する例

def read_all_files():

    yield from read_file1()  # file1のすべての値を返す

    yield from read_file2()  # file2のすべての値を返す

 

# 使用例

for line in read_all_files():

    print(line)

まとめ

Pythonのyieldは、メモリ効率に優れたジェネレーター関数を作成するキーワードです。大容量データの処理や無限シーケンスの生成に最適で、遅延評価により計算コストを削減できます。

ぜひ実際のプロジェクトでyieldを活用してみてください。