ジェネレータ式とリスト内包表記


412

Pythonではいつジェネレーター式を使用し、リスト内包表記を使用する必要がありますか?

# Generator expression
(x*2 for x in range(256))

# List comprehension
[x*2 for x in range(256)]

28
可能性[exp for x in iter]だけのために砂糖ことlist((exp for x in iter))?または実行の違いはありますか?
b0fh 2013年

1
それは私が関連した質問を持っていると思うので、yieldを使用するとき、関数からのジェネレーター式だけを使用できますか、ジェネレーターオブジェクトを返す関数にyieldを使用する必要がありますか?

28
@ b0fhコメントへの回答が非常に遅い:Python2では小さな違いがあり、ループ式はリスト内包からリークしますが、ジェネレーター式はリークしません。と比較X = [x**2 for x in range(5)]; print xするY = list(y**2 for y in range(5)); print yと、2番目はエラーになります。Python3では、リスト内包は確かにlist()期待どおりに供給されたジェネレータ式の構文糖であり、ループ変数がリークすることはもうありません
Bas Swinckels 2014年

13
PEP 0289を読むことをお勧めします。「このPEPは、ジェネレータ式を、高性能でメモリ効率の良いリスト内包とジェネレータの一般化として紹介します」と要約します。また、それらを使用するときに役立つ例もあります。
icc97 2016年

5
@ icc97パーティーにも8年遅れており、PEPリンクは完璧でした。見つけやすくしてくれてありがとう!
eenblam

回答:


283

Johnの答えは適切です(そのリスト内包表記は、何かを何度も繰り返し処理したい場合に適しています)。ただし、リストメソッドのいずれかを使用する場合は、リストを使用する必要があることにも注意してください。たとえば、次のコードは機能しません。

def gen():
    return (something for something in get_some_stuff())

print gen()[:2]     # generators don't support indexing or slicing
print [5,6] + gen() # generators can't be added to lists

基本的には、実行する処理が1回だけである場合はジェネレータ式を使用します。生成された結果を保存して使用したい場合は、おそらくリストの理解力が良いでしょう。

パフォーマンスがどちらか一方を選択する最も一般的な理由であるため、私のアドバイスは、それを心配せずに1つだけを選択することです。プログラムの実行速度が遅すぎることがわかった場合は、その場合にのみ、戻ってコードのチューニングについて心配する必要があります。


70
たとえば、yieldを使用して協調スケジューリングを行うコルーチンを作成している場合など、ジェネレーターを使用する必要がある場合があります。しかし、それをしているのなら、おそらくこの質問をしていません;)
ephemient

12
私はこれが古いことを知っていますが、ジェネレータ(および反復可能なもの)を拡張機能を使用してリストに追加できることは注目に値します:-aは[1、2、3、4、5、6 a = [1, 2, 3] b = [4, 5, 6] a.extend(b)]になります。(コメントに改行を追加できますか??)
jarvisteve 2012

12
@jarvisteveあなたの例はあなたが言っている言葉を偽っています。ここにも細かい点があります。リストはジェネレーターで拡張できますが、それをジェネレーターにすることには意味がありませんでした。ジェネレーターをリストで拡張することはできず、ジェネレーターは反復可能ではありません。a = (x for x in range(0,10)), b = [1,2,3]例えば。a.extend(b)例外をスローします。b.extend(a)aのすべてを評価します。その場合、そもそもそれをジェネレーターとしても意味がありません。
Slater Victoroff 2013年

4
@SlaterTyranusあなたは100%正解であり、私は正確さのためにあなたを賛成しました。それにもかかわらず、彼のコメントはOPの質問への非回答として有用であると思います。なぜなら、「リスト生成機能付きの結合ジェネレーター」のようなものを検索エンジンに入力したので、ここにいる人を助けるためです。
rbp

1
ジェネレーターを使用して1回反復する理由(たとえば、メモリ不足に関する私の懸念が、値を1つずつ「フェッチする」ことに関する私の懸念を無効にする)は、おそらく複数回反復するときにも当てはまりますか?リストをより便利にするかもしれませんが、それがメモリの懸念を上回るのに十分かどうかは別の問題です。
ロブ・グラント

181

ジェネレータ式またはリスト内包を反復することは同じことを行います。ただし、リスト内包表記は最初にメモリ内にリスト全体を作成し、ジェネレータ式はオンザフライでアイテムを作成するため、非常に大きな(そして無限!)シーケンスにそれを使用できます。


39
+1は無限です。パフォーマンスを気にしなくても、リストでそれを行うことはできません。
Paul Draper

内包法を使用して無限ジェネレーターを作成できますか?
AnnanFay

5
@Annanすでに別の無限ジェネレーターにアクセスできる場合のみ。例えば、itertools.count(n)Nから出発して、整数の無限配列であるので、(2 ** item for item in itertools.count(n))の力の無限配列であろう2から始まります2 ** n
ケビン

2
ジェネレーターは、繰り返し処理された後、アイテムをメモリから削除します。たとえば、ビッグデータがある場合は、表示したいだけです。それは記憶を独り占めしない。ジェネレーターでは、アイテムは「必要に応じて」処理されます。リストにこだわるか、リストを繰り返し処理する(つまり、アイテムを保存する)場合は、リスト内包表記を使用します。
j2emanue 2015年

102

結果を複数回繰り返す必要がある場合、または速度が最も重要な場合は、リスト内包表記を使用します。範囲が大きいか無限であるジェネレータ式を使用します。

見る ジェネレータ式とリスト内包表記をしてください。


2
これはおそらく少し話題から外れますが、残念ながら「グーグル化できません」...このコンテキストで「最重要」とはどういう意味ですか?私は英語のネイティブスピーカーではありません... :)
ギジェルモアレス

6
@GuillermoAresこれは、最重要の意味での「グーグル」の直接的な結果です。何よりも重要です。最高。
Sнаđошƒаӽ

1
だから、listsよりスピーディあるgenerator表現?dFの答えを読んで、それが逆であることに気づきました。
Hassan Baig 2016

1
範囲が小さいほどリスト内包表記は速くなると言ったほうがいいでしょうが、スケールが大きくなると、その場で値を計算するほうが価値があります。それがジェネレータ式が行うことです。
カイル

59

重要な点は、リスト内包表記が新しいリストを作成することです。ジェネレーターは反復可能なオブジェクトを作成します。これは、ビットを消費するときにオンザフライでソースマテリアルを「フィルター」します。

「hugefile.txt」という2TBのログファイルがあり、「ENTRY」という単語で始まるすべての行の内容と長さが必要だとします。

したがって、リスト内包表記を作成することから始めます。

logfile = open("hugefile.txt","r")
entry_lines = [(line,len(line)) for line in logfile if line.startswith("ENTRY")]

これにより、ファイル全体が丸められ、各行が処理され、一致する行が配列に格納されます。したがって、このアレイには最大2TBのコンテンツを含めることができます。これは大量のRAMであり、おそらくあなたの目的には実用的ではありません。

代わりに、ジェネレータを使用してコンテンツに「フィルタ」を適用できます。結果の反復を開始するまで、実際にはデータは読み取られません。

logfile = open("hugefile.txt","r")
entry_lines = ((line,len(line)) for line in logfile if line.startswith("ENTRY"))

まだ1行もファイルから読み込まれていません。実際、結果をさらにフィルタリングしたいとします。

long_entries = ((line,length) for (line,length) in entry_lines if length > 80)

まだ何も読み込まれていませんが、ここでは、希望どおりにデータを操作する2つのジェネレータを指定しました。

フィルター処理された行を別のファイルに書き込みます。

outfile = open("filtered.txt","a")
for entry,length in long_entries:
    outfile.write(entry)

次に、入力ファイルを読み取ります。私たちのようforループが追加の行を要求し続け、long_entries発電機からのラインを要求しentry_lines、その長さが80文字を超えるものだけを返す、発電機。そして今度は、entry_linesジェネレーターは(示されているようにフィルターされた)行をlogfileイテレーターイテレーターがファイルを読み取ります。

したがって、完全に入力されたリストの形式でデータを出力関数に「プッシュ」する代わりに、必要な場合にのみ出力関数にデータを「プル」する方法を提供します。私たちの場合、これははるかに効率的ですが、それほど柔軟ではありません。ジェネレーターは一方向、ワンパスです。読み込んだログファイルのデータはすぐに破棄されるため、前の行に戻ることはできません。一方、データを使い終わったら、データを保持する必要はありません。


46

ジェネレータ式の利点は、リスト全体を一度に作成しないため、使用するメモリが少ないことです。ジェネレータ式は、リストが結果の合計や結果からの辞書の作成などの仲介者である場合に最適に使用されます。

例えば:

sum(x*2 for x in xrange(256))

dict( (k, some_func(k)) for k in some_list_of_keys )

利点は、リストが完全に生成されないため、メモリがほとんど使用されないことです(また、より高速になるはずです)。

ただし、目的の最終製品がリストである場合は、リスト内包表記を使用する必要があります。生成されたリストが必要なので、ジェネレータ式を使用してメモリを保存しません。また、並べ替えや反転などのリスト関数を使用できるという利点もあります。

例えば:

reversed( [x*2 for x in xrange(256)] )

9
言語には、ジェネレータ式がそのように使用されることを意図しているというヒントがあります。ブラケットをなくします!sum(x*2 for x in xrange(256))
u0b34a0f6ae 2009

8
sortedそして、reversed任意の反復可能、ジェネレータ式の作業罰金が含まれています。
marr75 2013年

1
あなたが2.7を使用して、上記のことができれば、そのdictの()の例では、ルックスが良いのdictの理解として(ジェネレータ式PEPその後、より古いことのためにPEPが、土地に長くかかった)だろう
ユルゲン・A.エアハルト

14

可変オブジェクト(リストなど)からジェネレータを作成する場合、ジェネレータは、ジェネレータの作成時ではなく、ジェネレータの使用時にリストの状態で評価されることに注意してください。

>>> mylist = ["a", "b", "c"]
>>> gen = (elem + "1" for elem in mylist)
>>> mylist.clear()
>>> for x in gen: print (x)
# nothing

リストが変更される可能性がある場合(またはそのリスト内の変更可能なオブジェクト)、ジェネレーターの作成時の状態が必要な場合は、代わりにリスト内包を使用する必要があります。


1
そして、これは受け入れられる答えになるはずです。データが使用可能なメモリよりも大きい場合は、常にジェネレータを使用する必要がありますが、メモリ内のリストをループする方が高速になる場合があります(ただし、そのための十分なメモリがありません)。
Marek Marczak


4

私はHadoop Mincemeatモジュールを使用しています。これは、次の点に注意してください。

import mincemeat

def mapfn(k,v):
    for w in v:
        yield 'sum',w
        #yield 'count',1


def reducefn(k,v): 
    r1=sum(v)
    r2=len(v)
    print r2
    m=r1/r2
    std=0
    for i in range(r2):
       std+=pow(abs(v[i]-m),2)  
    res=pow((std/r2),0.5)
    return r1,r2,res

ここで、ジェネレーターはテキストファイル(最大15GB)から数値を取得し、Hadoopのmap-reduceを使用してこれらの数値に単純な計算を適用します。収量関数ではなくリスト内包表記を使用した場合、合計と平均の計算にはるかに長い時間がかかります(スペースの複雑さは言うまでもありません)。

Hadoopは、ジェネレーターのすべての利点を活用するための優れた例です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.