CodeGym /Javaコース /Python SELF JA /ジェネレーター

ジェネレーター

Python SELF JA
レベル 13 , レッスン 4
使用可能

3.1 ジェネレーターとの出会い

ジェネレーターiteratorオブジェクトを返す関数のことだよ。これらのiteratorは、リクエストに応じて値を生成するから、大量のデータセットをメモリに完全に読み込まずに処理できるんだ。

ジェネレーターの作り方はいくつかあるけど、ここでは一番ポピュラーなものを紹介するね。

関数を使ったジェネレーター

ジェネレーターは関数内でyieldキーワードを使うことで作成されるよ。yieldを持つ関数が呼び出されると、ジェネレーターオブジェクトを返すけど、その瞬間には関数内のコードは実行されないんだ。それよりもyieldの式で実行が停止して、ジェネレーターオブジェクトの__next__()メソッドが呼ばれるごとに再開されるんだ。


def count_up_to(max):
    count = 1
    while count <= max:
        yield count
        count += 1
        
counter = count_up_to(5)
print(next(counter))  # 出力: 1
print(next(counter))  # 出力: 2
print(next(counter))  # 出力: 3
print(next(counter))  # 出力: 4
print(next(counter))  # 出力: 5

関数にyieldがある場合、Pythonはその関数を通常の関数の実行の代わりに、実行状態を管理するジェネレーターオブジェクトを作成するんだ。

ジェネレーター式

ジェネレーター式はリスト内包表記に似ているけど、角括弧の代わりに丸括弧を使って作成されるんだ。これらもジェネレーターオブジェクトを返すよ。


squares = (x ** 2 for x in range(10))

print(next(squares))  # 出力: 0
print(next(squares))  # 出力: 1
print(next(squares))  # 出力: 4

どの方法が好き?

3.2 ジェネレーターの利点

メモリの効率的な使用

ジェネレーターはオンデマンドで値を計算するから、大量のデータをメモリに完全に読み込まずに処理できるよ。これにより、ジェネレーターは大規模なデータセットやデータストリームを扱うのに最適な選択肢となるんだ。


def large_range(n):
    for i in range(n):
        yield i
        
for value in large_range(1000000):
    # 一度に一つの値を処理
    print(value)

遅延評価

ジェネレーターは遅延評価を行うので、必要なときにのみ値を計算するんだ。これにより、不必要な計算を避け、パフォーマンスが向上するんだ。


def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b
        
fib = fibonacci()
for _ in range(10):
    print(next(fib))

便利な構文

ジェネレーターはiteratorを作成するための便利な構文を提供していて、コードを書くのも読むのも簡単になるよ。

3.3 ジェネレーターの使用

標準ライブラリでのジェネレーターの使用例

Pythonの標準ライブラリでは、多くの関数がジェネレーターを使用しているよ。例えば、関数range()は、数のシーケンスを生成するジェネレーターオブジェクトを返すんだ。


for i in range(10):
    print(i)

うん、世界はもう変わったよね。

無限シーケンスの作成

ジェネレーターを使うと、無限に続くシーケンスを作成できるんだ。これは、無限のデータストリームを生成する時など、様々なシナリオで役立つよ。


def natural_numbers():
    n = 1
    while True:
        yield n
        n += 1
        
naturals = natural_numbers()
for _ in range(10):
    print(next(naturals))

send()close()の使用

ジェネレーターオブジェクトはsend()close()メソッドをサポートしていて、これらを使って値をジェネレーターに送り返したり、実行を終了させることができるんだ。


def echo():
    while True:
        received = yield
        print(received)
        
e = echo()
next(e)  # ジェネレーターを始動させる
e.send("Hello, world!")  # 出力: Hello, world!
e.close()

3.4 実践でのジェネレーター

ジェネレーターと例外

ジェネレーターは例外処理もできるから、より堅牢なコードを書くための強力なツールになるよ。


def controlled_execution():
    try:
        yield "Start"
        yield "Working"
    except GeneratorExit:
        print("ジェネレーターが閉じられた")
        
gen = controlled_execution()
print(next(gen))  # 出力: Start
print(next(gen))  # 出力: Working
gen.close()  # 出力: ジェネレーターが閉じられた

例外の扱いについては今後のレクチャーで見ていくけど、ジェネレーターがうまくそれらを扱えることを知っておくと役に立つよ。

入れ子のジェネレーター

ジェネレーターは入れ子にできるから、複雑なイテレーション構造を作成することができるよ。


def generator1():
    yield from range(3)
    yield from "ABC"
        
for value in generator1():
    print(value)

# 出力
0
1
2
A
B
C

説明:

yield from: この構造は別のジェネレーターに動作を委譲するために使われ、コードを簡素化し、可読性を向上させるよ。

ジェネレーターとパフォーマンス

ジェネレーターを使うことで、メモリの使用を減らし、イテレーションの実行をより効率的にすることでプログラムのパフォーマンスを大幅に向上させることができるよ。

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


import time
import sys

def memory_usage(obj):
    return sys.getsizeof(obj)

n = 10_000_000

# リストの使用
start_time = time.time()
list_comp = [x ** 2 for x in range(n)]
list_time = time.time() - start_time
list_memory = memory_usage(list_comp)

# ジェネレーターの使用
start_time = time.time()
gen_comp = (x ** 2 for x in range(n))
gen_result = sum(gen_comp)  # 結果の比較のために合計を計算
gen_time = time.time() - start_time
gen_memory = memory_usage(gen_comp)

print(f"リスト:")
print(f"  時間: {list_time:.2f} 秒")
print(f"  メモリ: {list_memory:,} バイト")

print(f"\nジェネレーター:")
print(f"  時間: {gen_time:.2f} 秒")
print(f"  メモリ: {gen_memory:,} バイト")

リスト:
  時間: 0.62 秒
  メモリ: 89,095,160 バイト

ジェネレーター:
  時間: 1.13 秒
  メモリ: 200 バイト
1
Опрос
システム関数,  13 уровень,  4 лекция
недоступен
システム関数
システム関数
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION