問題の歴史
Pythonはバージョン2.4からリスト内包表記を「ジェネレーター式」で補完しました。これにより、ジェネレーターに似た遅延値の列を作成できるようになり、コンパクトで読みやすい形式で表現できます。
問題
リスト内包表記([x for x in iterable])は、すべての要素をメモリに即時に読み込むことでリストを作成します。要素の数が非常に大きい場合、これは非効率的または危険です。ジェネレーター関数(yieldを使用)はより柔軟ですが、関数の独自の定義とより多くのコード行が必要です。
解決策
ジェネレーター式((x for x in iterable))は、遅延列を生成するための簡潔な構文を提供します(要素は必要に応じて計算され、すべてが即座に読み込まれるわけではありません)。リスト内包表記に似ていますが、丸括弧を使用します。
# リスト内包表記はすべてをメモリに読み込みます squares_list = [x**2 for x in range(10**6)] # ジェネレーター式:要素はリクエストに応じて提供され、メモリの使用はほとんどありません squares_gen = (x**2 for x in range(10**6)) # ジェネレーターから最初の5つの値を取得する for _ in range(5): print(next(squares_gen))
主な特徴:
同じジェネレーター式を何度も「再通過」できますか?
いいえ、1回イテレーションすると、ジェネレーターは「消耗」します。再度通過するには、新しいジェネレーターを作成するか、リスト内包表記を使用する必要があります。
it = (x for x in range(3)) print(list(it)) # [0,1,2] print(list(it)) # [] — 値を取得できなくなります
ジェネレーターは使用間で状態を保持しますか?
はい、ジェネレーター式はnext()が呼び出されるたびに「位置」を保持します(または次のイテレーションの際に)が、最初の状態にリセットすることはできません。新しいオブジェクトを作成しない限り。
1つの行でジェネレーター式を同時に複数回使用できますか?
いいえ!ジェネレーターを同時にいくつかの場所(例えば、複数の関数に同時に返すなど)で「展開」すると、一部のデータが失われます — 各子の使用はポインタを前に進めます。
g = (x for x in range(3)) print(sum(g), list(g)) # sum(g)はすべてを取得し、list(g)は空のまま
大きなファイルを分析するプロジェクトで次のように使用しました:
data = (parse_line(line) for line in file) process(list(data)) other_process(list(data))
利点:
欠点:
データを再利用する必要がある場合はリスト内包表記を使用するか、一度だけ消費するためのジェネレーターを作成します:
# 一度だけ分析するためのジェネレーター(例えば、合計を計算する) total = sum(parse_line(line) for line in file)
利点:
欠点: