リスト内包表記対ラムダ+フィルター


857

たまたま、基本的なフィルタリングの必要性があることに気づきました。リストがあり、アイテムの属性でフィルタリングする必要があります。

私のコードは次のようになりました:

my_list = [x for x in my_list if x.attribute == value]

でも、こんなふうに書いた方がいいのではないかと思いました。

my_list = filter(lambda x: x.attribute == value, my_list)

より読みやすく、パフォーマンスに必要な場合はラムダを取り出して何かを取得できます。

質問です:2番目の方法を使用する際に注意点はありますか?パフォーマンスの違いはありますか?Pythonic Way™がまったくないので、さらに別の方法で実行する必要がありますか(ラムダの代わりにitemgetterを使用するなど)?


19
より適切な例は、述語として使用するための適切に名前が付けられた関数がすでにある場合です。その場合、filterもっと読みやすいと思う人はもっと多いと思います。listcompでそのまま使用できる単純な式があるが、に渡すためにラムダでラップする必要がある場合(または、同様にから、partialまたはoperator関数などで構成する必要がある場合)はfilter、listcompsが勝つときです。
abarnert 2013

3
Python3では少なくとも、filterリストではなくフィルタージェネレーターオブジェクトが返されることに注意してください。
Matteo Ferla

回答:


588

人によって美しさがどれほど違うかは不思議です。リストの内包表記はfilter+ よりも明確ですlambdaが、どちらでも簡単に使用できます。

の使用が遅くなる可能性があるのは2つありますfilter

1つ目は、関数呼び出しのオーバーヘッドです。Python関数(defまたはによって作成されたかどうかlambda)を使用するとすぐに、フィルターがリスト内包よりも遅くなる可能性があります。ほとんどの場合、問題は十分ではありません。コードの時間を計ってボトルネックになっていることがわかるまでは、パフォーマンスについてあまり考えるべきではありませんが、違いはそこにあります。

適用される可能性のある他のオーバーヘッドは、ラムダがスコープ付き変数(value)にアクセスすることを強制されていることです。これはローカル変数にアクセスするよりも遅く、Python 2.xではリスト内包表記はローカル変数にのみアクセスします。Python 3.xを使用している場合、リスト内包表記は別の関数で実行されるためvalue、クロージャーからもアクセスされ、この違いは適用されません。

考慮すべきもう1つのオプションは、リスト内包表記の代わりにジェネレータを使用することです。

def filterbyvalue(seq, value):
   for el in seq:
       if el.attribute==value: yield el

次に、メインコード(読みやすさが重要な部分です)で、リスト内包表記とフィルターの両方を、うまくいけば意味のある関数名に置き換えました。


68
ジェネレーターの+1。家に、素晴らしい発電機がいかにすばらしいかを示すプレゼンテーションへのリンクがあります。に変更[]するだけで、リスト内包表記をジェネレータ式に置き換えることもでき()ます。また、リストコンプがより美しいことにも同意します。
Wayne Werner

1
実際、いいえ-フィルターはより高速です。stackoverflow.com/questions/5998245/…の
skqr

2
@skqrはベンチマークに単にtimeitを使用する方が良いですがfilter、Pythonコールバック関数を使用するとより高速であることがわかる例を挙げてください。
ダンカン

8
@ tnq177ジェネレータに関するDavid Beasleyのプレゼンテーションです-dabeaz.com/generators
Wayne Werner

2
@VictorSchröderはい、多分私は不明瞭でした。私が言おうとしていたのは、メインコードで全体像を見ることができる必要があるということです。小さなヘルパー関数では、その1つの関数のみを気にする必要があります。他に外部で行われていることは無視できます。
ダンカン

237

これはPythonのやや宗教的な問題です。にもかかわらず、グイドは取り外しと考えmapfilterおよびreducePythonの3から、最終的にだけでバックラッシュのに十分だったreduceの組み込み関数から移動したfunctools.reduce

個人的には、リスト内包表記が読みやすくなっています。[i for i in list if i.attribute == value]すべての動作はフィルター関数の内部ではなく表面上にあるため、式から何が起こっているかがより明確になります。

2つのアプローチのパフォーマンスの違いはわずかであるため、あまり心配する必要はありません。これがアプリケーションのボトルネックである可能性が低いことが判明した場合のみ、これを最適化します。

また、BDFLfilterは言語から削除されることを望んでいたので、リストの内包表記が自動的にPythonicになるはずです;-)


1
グイドの入力へのリンクに感謝します。他に何もないのであれば、もう使用しないようにし、習慣を身につけないようにし、その宗教を支持しません:)
ダッシュ

1
しかし、reduceは単純なツールで行うのが最も複雑です!マップとフィルターは内包に置き換えるのは簡単です!
njzk2

8
Python3でreduceが降格されたことを知らなかった。洞察をありがとう!PySparkのように、reduce()は依然として分散コンピューティングで非常に役立ちます。私は間違いだったと思います...
Tagar

1
@Tagarでも、reduceを使用できます。functoolsからインポートする必要があります
icc97

69

速度の違いはごくわずかなので、フィルターを使用するか、リスト内包表記を使用するかは、好みの問題に帰着します。一般的に、私は理解力を使用する傾向があります(ここで他のほとんどの回答に同意するようです)が、私が好む1つのケースがありfilterます。

非常に頻繁に使用されるのは、述語P(x)に従って反復可能なXの値を引き出すことです。

[x for x in X if P(x)]

しかし、いくつかの関数を最初に値に適用したい場合があります:

[f(x) for x in X if P(f(x))]


具体的な例として、

primes_cubed = [x*x*x for x in range(1000) if prime(x)]

これはを使用するよりも少し良く見えると思いますfilter。しかし今考えなさい

prime_cubes = [x*x*x for x in range(1000) if prime(x*x*x)]

この場合はfilter、計算後の値に反対します。立方体を2度計算する問題(より高価な計算を想像してください)に加えて、式を2度書き、DRYの美観に違反する問題があります。この場合、私は使いがちです

prime_cubes = filter(prime, [x*x*x for x in range(1000)])

7
別のリスト内包表記を介して素数を使用することを検討しませんか?など[prime(i) for i in [x**3 for x in range(1000)]]
viki.omega9 2015年

20
x*x*x素数することはできません、それは持っているとして、x^2及びx要因として、一例では、実際に数学的な方法で、意味がありませんが、多分それはまだhelpulです。(たぶん、もっと良いものを見つけることができたのでしょうか?)
Zelphir Kaltstahl 2015

3
:我々はメモリを食べにしたくない場合は、我々は最後例えば代わりにジェネレータ式を使用することができることに注意prime_cubes = filter(prime, (x*x*x for x in range(1000)))
Mateen Ulhaq

4
@MateenUlhaqこれはprime_cubes = [1]、メモリとCPUサイクルの両方を節約するように最適化できます;-)
Dennis Krupenik

7
@DennisKrupenikまたはむしろ[]
Mateen Ulhaq

29

が、filterパフォーマンスが絶対的に重要でない限り、「より高速な方法」、そのようなことを気にしないだろう「Python的な方法」であってもよい(この場合には、あなたは、Pythonを使用してはならないでしょう!)。


9
よく見られる議論に対する後のコメント:分析を10時間ではなく5時間で実行することは時々違いを生み、それがpythonコードの最適化に1時間かかることで達成できる場合、それは価値があります(特に、 Pythonでは快適で、より高速な言語ではできません)。
bli

しかし、より重要なのは、ソースコードがそれを読んで理解しようとする速度をどれだけ遅くするかです。
thoni56

20

Python 3でそれを追加すると思います。filter()は実際にはイテレーターオブジェクトであるため、フィルターされたリストを作成するには、フィルターメソッドの呼び出しをlist()に渡す必要があります。だからPython 2では:

lst_a = range(25) #arbitrary list
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = filter(lambda num: num % 2 == 0, lst_a)

リストbとcは同じ値を持ち、filter()が同等である[zの場合はxのxのx]とほぼ同じ時間で完了しました。ただし、3では、この同じコードにより、リストcにはフィルターされたリストではなくフィルターオブジェクトが含まれます。3で同じ値を生成するには:

lst_a = range(25) #arbitrary list
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = list(filter(lambda num: num %2 == 0, lst_a))

問題は、list()が反復可能引数を引数として取り、その引数から新しいリストを作成することです。その結果、python 3でこのようにfilterを使用すると、filter()からの出力と元のリストを反復処理する必要があるため、[x for y in z if]メソッドの最大2倍の時間がかかります。


13

重要な違いは、リスト内包表記はa listを返すのに対し、フィルターはを返しますがfilter、これはaのように操作することはできませんlist(つまりlen、それを呼び出すと、の戻り値では機能しませんfilter)。

私自身の自己学習は私にいくつかの同様の問題をもたらしました。

ことで、結果の持つ方法があれば、言ったlistからfilter、あなたが行うときは、.NETでどうなるようなビットlst.Where(i => i.something()).ToList()、私はそれを知って好奇心旺盛です。

編集:これは2ではなくPython 3の場合です(コメントの説明を参照)。


4
filterはリストを返し、lenを使用できます。少なくとも私のPython 2.7.6では。
thiruvenkadam

7
これは、Python 3にはそうではありません a = [1, 2, 3, 4, 5, 6, 7, 8] f = filter(lambda x: x % 2 == 0, a) lc = [i for i in a if i % 2 == 0] >>> type(f) <class 'filter'> >>> type(lc) <class 'list'>
Adeynack

3
「結果のリストを取得する方法がある場合...それを知りたいのですが」。list()結果を呼び出すだけです:list(filter(my_func, my_iterable))。そしてもちろん、あなたは置き換えることができますlistset、あるいはtuple、または反復可能になります何か。しかし、関数型プログラマ以外の誰にとっても、へのfilter明示的な変換に加えて、リスト内包表記を使用するほうがより強力listです。
Steve Jessop

10

2番目の方法の方が読みやすいと思います。それは、意図が何であるかを正確に伝えます:リストをフィルタリングします。
PS:変数名として「リスト」を使用しないでください


7

filter組み込み関数を使用する場合、通常は少し高速です。

あなたの場合、リストの理解が少し速くなると思います


python -m timeit 'filter(lambda x:x in [1,2,3,4,5]、range(10000000))' 10ループ、最高3:ループあたり1.44秒python -m timeit '[x for x in range(10000000)if x in [1,2,3,4,5]] '10ループ、ベスト3:ループあたり860ミリ秒
ギアオスダウ2014年

@sepdau、ラムダ関数は組み込みではありません。リスト内包表記は過去4年間で改善されました。組み込み関数を使用しても、違いはごくわずかです
John La Rooy 2014年

7

フィルターはそれだけです。リストの要素を除外します。あなたは定義が同じことを言及しているのを見ることができます(前に述べた公式のドキュメントリンクで)一方、リスト内包表記は、前のリストに基づいて新しいリストを作成するものです(フィルターとリスト内包表記の両方が新しいリストを作成し、古いリストの代わりに操作を実行しません。ここでの新しいリストは、たとえば、完全に新しいデータ型です。整数を文字列に変換するようなものです)

あなたの例では、定義に従って、リスト内包よりもフィルタを使用することをお勧めします。ただし、必要に応じて、リスト要素のother_attributeを使用し、例では新しいリストとして取得する場合は、リスト内包表記を使用できます。

return [item.other_attribute for item in my_list if item.attribute==value]

これは、フィルターとリストの理解について実際に覚えている方法です。リスト内のいくつかのものを削除し、他の要素はそのままにして、フィルターを使用します。要素で独自のロジックを使用し、何らかの目的に適した簡略化されたリストを作成します。リストの理解を使用します。


2
今後投票を繰り返さないように、投票の理由を教えていただければ幸いです。
thiruvenkadam

フィルターとリストの理解の定義は、それらの意味が議論されていなかったため、必要ありませんでした。リスト内包表記が「新しい」リストにのみ使用されるべきであるということは提示されていますが、議論されていません。
Agos 2015

この定義を使用して、フィルターはケースに当てはまる同じ要素を含むリストを提供するが、リストを理解することで、intをstrに変換するなど、要素自体を変更できると言いました。しかし、要点:-)
thiruvenkadam

4

これは、リストの理解ので何かをフィルタリングする必要があるときに使用する短い部分です。フィルター、ラムダ、リストの組み合わせだけです(猫の忠誠度と犬の清潔さとも呼ばれます)。

この場合、ファイルを読み取って、空白行、コメント化された行、および行のコメントの後のすべてを削除します。

# Throw out blank lines and comments
with open('file.txt', 'r') as lines:        
    # From the inside out:
    #    [s.partition('#')[0].strip() for s in lines]... Throws out comments
    #   filter(lambda x: x!= '', [s.part... Filters out blank lines
    #  y for y in filter... Converts filter object to list
    file_contents = [y for y in filter(lambda x: x != '', [s.partition('#')[0].strip() for s in lines])]

これにより、非常に少ないコードで多くのことを実現できます。1行ではロジックが多すぎて簡単に理解できないかもしれませんが、読みやすさは重要です。
Zelphir Kaltstahl、2015

あなたはこれを次のように書くことができますfile_contents = list(filter(None, (s.partition('#')[0].strip() for s in lines)))
スティーブ・ジェソップ

4

受け入れられた回答に加えて、リスト内包表記の代わりにフィルターを使用すべき場合があります。リストがハッシュ化できない場合、リスト内包表記で直接処理することはできません。実際の例はpyodbc、データベースから結果を読み取るために使用する場合です。のfetchAll()結果cursorはハッシュ化できないリストです。この場合、返された結果を直接操作するには、フィルターを使用する必要があります。

cursor.execute("SELECT * FROM TABLE1;")
data_from_db = cursor.fetchall()
processed_data = filter(lambda s: 'abc' in s.field1 or s.StartTime >= start_date_time, data_from_db) 

ここでリスト内包表記を使用すると、エラーが発生します。

TypeError:unhashable type: 'list'


1
すべてのリストは非ハッシュです>>> hash(list()) # TypeError: unhashable type: 'list':第二に、これは正常に動作しますprocessed_data = [s for s in data_from_db if 'abc' in s.field1 or s.StartTime >= start_date_time]
トーマス・グレインジャー

「リストがハッシュ化できない場合、リスト内包表記で直接処理することはできません。」これは真実ではなく、とにかくすべてのリストはハッシュ化できません。
juanpa.arrivillaga

3

それは私に慣れ得るためにいくつかの時間かかったhigher order functions filterとしますmap。だから私はそれらに慣れました、そしてfilterそれが真実であるものを何でも保つことによってフィルタリングすることは明白であり、私はいくつかのfunctional programming用語を知っていることをクールに感じたので、私は実際に好きでした。

次に、この一節(Fluent Python Book)を読みました。

map関数とfilter関数はPython 3にまだ組み込まれていますが、リスト内包表記とジェネレータ式の導入以来、それらはそれほど重要ではありません。listcompまたはgenexpは、マップとフィルターを組み合わせた役割を果たしますが、より読みやすくなっています。

そして今、私は、リストの理解のようなすでに広く普及しているイディオムでそれを達成できるのなら、なぜfilter/ の概念に悩むのかと思います map。さらにmapsfilters関数の一種です。この場合、Anonymous functionsラムダを使用することを好みます。

最後に、テストするために、両方の方法(mapおよびlistComp)の時間を測定しましたが、それについて議論することを正当化するような関連する速度の違いはありませんでした。

from timeit import Timer

timeMap = Timer(lambda: list(map(lambda x: x*x, range(10**7))))
print(timeMap.timeit(number=100))

timeListComp = Timer(lambda:[(lambda x: x*x) for x in range(10**7)])
print(timeListComp.timeit(number=100))

#Map:                 166.95695265199174
#List Comprehension   177.97208347299602

0

不思議なことに、Python 3ではフィルターがリスト内包よりも高速に実行されます。

リスト内包表記の方がパフォーマンスが高いといつも思っていました。次のようなもの:[nameがNoneでない場合、brand_names_dbの名前の名前]生成されるバイトコードは少し良いです。

>>> def f1(seq):
...     return list(filter(None, seq))
>>> def f2(seq):
...     return [i for i in seq if i is not None]
>>> disassemble(f1.__code__)
2         0 LOAD_GLOBAL              0 (list)
          2 LOAD_GLOBAL              1 (filter)
          4 LOAD_CONST               0 (None)
          6 LOAD_FAST                0 (seq)
          8 CALL_FUNCTION            2
         10 CALL_FUNCTION            1
         12 RETURN_VALUE
>>> disassemble(f2.__code__)
2           0 LOAD_CONST               1 (<code object <listcomp> at 0x10cfcaa50, file "<stdin>", line 2>)
          2 LOAD_CONST               2 ('f2.<locals>.<listcomp>')
          4 MAKE_FUNCTION            0
          6 LOAD_FAST                0 (seq)
          8 GET_ITER
         10 CALL_FUNCTION            1
         12 RETURN_VALUE

しかし、実際には遅いです:

   >>> timeit(stmt="f1(range(1000))", setup="from __main__ import f1,f2")
   21.177661532000116
   >>> timeit(stmt="f2(range(1000))", setup="from __main__ import f1,f2")
   42.233950221000214

8
無効な比較。まず、ラムダ関数をフィルターバージョンに渡さないため、デフォルトで恒等関数になります。定義するときにif not Noneリストの内包にあなたがしているラムダ関数を定義する(気づく MAKE_FUNCTION文)。次に、リスト内包バージョンはNone値のみを削除するため、結果は異なりますが、フィルターバージョンはすべての「偽の」値を削除します。そうは言っても、マイクロベンチマークの目的全体は無意味です。これらは100万回の反復で、1kアイテムです。違いはごくわずかです。
VictorSchröder19年

-7

私の見解

def filter_list(list, key, value, limit=None):
    return [i for i in list if i[key] == value][:limit]

3
iとは一度も言われdictず、必要もありませんlimit。それ以外に、これはOPが提案したものとどのように異なり、質問にどのように答えますか?
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.