理解度とマップのリスト


732

map()リスト内包表記を使用すること、またはその逆を好む理由はありますか?それらのどちらかは、一般的に効率的ですか、または一般的に他のものよりパイソンのように見なされますか?


8
リスト内包表記の代わりにマップを使用するとPyLintが警告することに注意してください。メッセージW0141を参照してください。
ランブリック2013年

2
@lumbric、わかりませんが、マップでラムダが使用されている場合のみです。
0xc0de 2017年

回答:


661

map場合によっては微視的に高速になることがあります(目的のためにラムダを作成していないが、mapとlistcompで同じ関数を使用している場合)。リスト内包表記は、他のケースではより高速になる場合があり、ほとんどの(すべてではない)pythonistaは、それらをより直接的かつ明確であると見なします。

まったく同じ関数を使用する場合のマップの小さな速度の利点の例:

$ python -mtimeit -s'xs=range(10)' 'map(hex, xs)'
100000 loops, best of 3: 4.86 usec per loop
$ python -mtimeit -s'xs=range(10)' '[hex(x) for x in xs]'
100000 loops, best of 3: 5.58 usec per loop

mapがラムダを必要とするときにパフォーマンスの比較が完全に逆転する方法の例:

$ python -mtimeit -s'xs=range(10)' 'map(lambda x: x+2, xs)'
100000 loops, best of 3: 4.24 usec per loop
$ python -mtimeit -s'xs=range(10)' '[x+2 for x in xs]'
100000 loops, best of 3: 2.32 usec per loop

39
はい、確かに私たちの社内のPythonスタイルガイドは、mapとfilterに対してlistcompsを明示的に推奨しています(場合によっては、小さくても測定可能なパフォーマンス向上マップが与える可能性があることには言及していません;-)。
Alex Martelli、

46
Alexの無限のスタイルポイントをキバッシュするのではなく、マップが読みやすくなる場合があります:data = map(str、some_list_of_objects)。他のいくつか... operator.attrgetter、operator.itemgetterなど
Gregg Lind

57
map(operator.attrgetter('foo'), objs)より読みやすい[o.foo for o in objs]?!
Alex Martelli、

52
@アレックス:私はoここのような不要な名前を導入しないことを好み、あなたの例はその理由を示しています。
リードバートン

29
でも、@ GreggLindにはポイントがあると思いますが、彼のstr()例ではそうです。
Eric O Lebigot、2011年

474

ケース

  • 一般的なケース:ほとんどの場合Pythonでリスト内包表記を使用することをお勧めします。これは、コードを読んでいる初心者プログラマーが行っていることがより明確になるためです。(これは他のイディオムが適用される可能性がある他の言語には適用されません。)リスト内包表記は反復のためのPythonの事実上の標準であるため、Pythonプログラマーが何をしているのかはさらに明白になります。彼らは期待されています。
  • あまり一般的ではないケース:ただし、すでに関数が定義されている場合はmap、「非Pythonic」と見なされますが、使用するのが妥当な場合がよくあります。たとえば、map(sum, myLists)はよりエレガント/簡潔です[sum(x) for x in myLists]。反復するためだけに2回入力する必要があるダミー変数(例:sum(x) for x...or sum(_) for _...またはsum(readableName) for readableName...)を構成する必要がないという優雅さが得られます。同じ引数が成立するfilterreduce、何からitertoolsモジュール:あなたはすでに便利な機能を持っている場合、あなたは先に行くと、いくつかの機能プログラミングを行うことができます。これはいくつかの状況では可読性を高め、他の状況ではそれを失います(例:初心者プログラマ、複数の引数)...しかし、コードの可読性はとにかくコメントに大きく依存します。
  • ほとんど絶対にありませんmap関数型プログラミングを行っている間、関数を純粋な抽象関数として使用したい場合があります。マッピングmapやカリー化を行っているmap場合、またはmap関数として説明することでメリットを得られる場合があります。たとえばHaskellでは、呼ばれるファンクターインターフェイスはfmap、あらゆるデータ構造に対するマッピングを一般化します。これはpythonでは非常に一般的ではありません。なぜなら、python文法ではジェネレータースタイルを使用して反復について説明する必要があるからです。簡単に一般化することはできません。(これは時々良いこともあれば悪いこともあります。)あなたがおそらくmap(f, *lists)合理的なことである珍しいpythonの例を思い付くでしょう。私が思いつくことができる最も近い例sumEach = partial(map,sum)は、です。これは、次のコードとほぼ同等のワンライナーです。

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • for-ループを使用するだけ:もちろん、forループを使用することもできます。関数型プログラミングの観点からはそれほどエレガントではありませんが、非ローカル変数を使用すると、Pythonなどの命令型プログラミング言語でコードがより明確になることがあります。forループは、一般に、リスト内包やマップが最適化されているようなリストを作成していない複雑な操作を単に実行している場合(たとえば、合計、またはツリーの作成など)に最も効率的です-少なくともメモリの点で効率的です(必ずしも時間に関してではなく、最悪の場合、まれな病理学的ガベージコレクションの占有を除外して一定の要素を期待します)。

「Pythonism」

「pythonic」という言葉が嫌いなのは、pythonicがいつもエレガントであるとは思えないからです。それでも、mapそしてfilter(そして非常に便利なitertoolsモジュールのような)および同様の関数は、おそらくスタイルの点でunpythonicと見なされます。

怠惰

効率の面では、ほとんどの関数型プログラミング構造と同様に、MAPはLAZYであり、実際にはpythonで遅延します。つまり、これを(python3で)行うことができ、コンピューターがメモリ不足にならず、保存されていないデータをすべて失うことはありません。

>>> map(str, range(10**100))
<map object at 0x2201d50>

リスト内包表記でそれをやってみてください:

>>> [str(n) for n in range(10**100)]
# DO NOT TRY THIS AT HOME OR YOU WILL BE SAD #

リスト内包表記も本質的にレイジーですが、Pythonはそれらを非レイジーとして実装することを選択したことに注意してください。それにもかかわらず、Pythonは次のように、ジェネレータ式の形式で遅延リスト内包表記をサポートしています。

>>> (str(n) for n in range(10**100))
<generator object <genexpr> at 0xacbdef>

基本的に、[...]構文はのようにジェネレータ式をリストコンストラクタに渡すと考えることができますlist(x for x in range(5))

短い工夫された例

from operator import neg
print({x:x**2 for x in map(neg,range(5))})

print({x:x**2 for x in [-y for y in range(5)]})

print({x:x**2 for x in (-y for y in range(5))})

リスト内包表記は遅延がないため、より多くのメモリが必要になる場合があります(ジェネレータ内包表記を使用する場合を除く)。角かっこを使用[...]すると、特にかっこが大量にある場合に、多くのことがわかりやすくなります。一方、タイピングのように冗長になることがあります[x for x in...。イテレータ変数を短くしておけば、コードをインデントしなければ、リストの内包は通常、より明確になります。しかし、いつでもコードをインデントできます。

print(
    {x:x**2 for x in (-y for y in range(5))}
)

または物事を分割します。

rangeNeg5 = (-y for y in range(5))
print(
    {x:x**2 for x in rangeNeg5}
)

python3の効率比較

map 今は怠惰です:

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=map(f,xs)'
1000000 loops, best of 3: 0.336 usec per loop            ^^^^^^^^^

したがって、すべてのデータを使用しない場合、または事前に必要なデータ量がわからない場合、mappython3(およびpython2またはpython3のジェネレーター式)では、必要な最後の瞬間までそれらの値を計算しません。通常、これは通常、使用によるオーバーヘッドを上回るmap。欠点は、ほとんどの関数型言語とは対照的に、Pythonではこれが非常に制限されることです。Pythonジェネレーターの式は順序でしか評価できないため、データに「順序どおり」に左から右にアクセスした場合にのみ、この利点が得られますx[0], x[1], x[2], ...

ただし、事前に作成しfたい関数がmapありmap、すぐにで評価を強制することによって、その怠惰を無視するとしlist(...)ます。非常に興味深い結果が得られます。

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=list(map(f,xs))'                                                                                                                                                
10000 loops, best of 3: 165/124/135 usec per loop        ^^^^^^^^^^^^^^^
                    for list(<map object>)

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=[f(x) for x in xs]'                                                                                                                                      
10000 loops, best of 3: 181/118/123 usec per loop        ^^^^^^^^^^^^^^^^^^
                    for list(<generator>), probably optimized

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=list(f(x) for x in xs)'                                                                                                                                    
1000 loops, best of 3: 215/150/150 usec per loop         ^^^^^^^^^^^^^^^^^^^^^^
                    for list(<generator>)

結果はAAA / BBB / CCCの形式で、Aはpython 3。?。?を使用した2010年頃のIntelワークステーションで実行され、BとCはpython 3.2.1を使用した2013年頃のAMDワークステーションで実行されました。非常に異なるハードウェアで。結果として、マップとリストの理解度は、他のランダムな要因の影響を最も強く受けるパフォーマンスで同等であるようです。私たちに言えることは、奇妙なことに、リスト内包[...]表記がジェネレータ式よりも優れていることを期待していることです(...)mapジェネレータ式は、(再びすべての値が/評価を使用していると仮定して)ことも、より効率的です。

これらのテストは非常に単純な関数(恒等関数)を想定していることを理解することが重要です。ただし、関数が複雑な場合、プログラムの他の要素と比較してパフォーマンスのオーバーヘッドは無視できるため、これは問題ありません。(のような他の単純なものでテストすることはまだ興味深いかもしれませんf=lambda x:x+x

Pythonアセンブリを読むことに長けている場合は、disモジュールを使用して、それが実際に舞台裏で起こっていることかどうかを確認できます。

>>> listComp = compile('[f(x) for x in xs]', 'listComp', 'eval')
>>> dis.dis(listComp)
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x2511a48, file "listComp", line 1>) 
              3 MAKE_FUNCTION            0 
              6 LOAD_NAME                0 (xs) 
              9 GET_ITER             
             10 CALL_FUNCTION            1 
             13 RETURN_VALUE         
>>> listComp.co_consts
(<code object <listcomp> at 0x2511a48, file "listComp", line 1>,)
>>> dis.dis(listComp.co_consts[0])
  1           0 BUILD_LIST               0 
              3 LOAD_FAST                0 (.0) 
        >>    6 FOR_ITER                18 (to 27) 
              9 STORE_FAST               1 (x) 
             12 LOAD_GLOBAL              0 (f) 
             15 LOAD_FAST                1 (x) 
             18 CALL_FUNCTION            1 
             21 LIST_APPEND              2 
             24 JUMP_ABSOLUTE            6 
        >>   27 RETURN_VALUE

 

>>> listComp2 = compile('list(f(x) for x in xs)', 'listComp2', 'eval')
>>> dis.dis(listComp2)
  1           0 LOAD_NAME                0 (list) 
              3 LOAD_CONST               0 (<code object <genexpr> at 0x255bc68, file "listComp2", line 1>) 
              6 MAKE_FUNCTION            0 
              9 LOAD_NAME                1 (xs) 
             12 GET_ITER             
             13 CALL_FUNCTION            1 
             16 CALL_FUNCTION            1 
             19 RETURN_VALUE         
>>> listComp2.co_consts
(<code object <genexpr> at 0x255bc68, file "listComp2", line 1>,)
>>> dis.dis(listComp2.co_consts[0])
  1           0 LOAD_FAST                0 (.0) 
        >>    3 FOR_ITER                17 (to 23) 
              6 STORE_FAST               1 (x) 
              9 LOAD_GLOBAL              0 (f) 
             12 LOAD_FAST                1 (x) 
             15 CALL_FUNCTION            1 
             18 YIELD_VALUE          
             19 POP_TOP              
             20 JUMP_ABSOLUTE            3 
        >>   23 LOAD_CONST               0 (None) 
             26 RETURN_VALUE

 

>>> evalledMap = compile('list(map(f,xs))', 'evalledMap', 'eval')
>>> dis.dis(evalledMap)
  1           0 LOAD_NAME                0 (list) 
              3 LOAD_NAME                1 (map) 
              6 LOAD_NAME                2 (f) 
              9 LOAD_NAME                3 (xs) 
             12 CALL_FUNCTION            2 
             15 CALL_FUNCTION            1 
             18 RETURN_VALUE 

[...]構文よりも構文を使用する方が良いようですlist(...)。悲しいことに、このmapクラスは逆アセンブルに対して少し不透明ですが、スピードテストで納得することができます。


5
「非常に有用なitertoolsモジュールは、おそらくスタイルの点でunpythonicと見なされています」。うーん。私は「Pythonic」という用語も好きではないので、ある意味でそれが何を意味するのかは気にしませんが、「Pythonicness」ビルトインmapとそれに従って使用する人にとってそれは公平ではないと思いますfilter標準ライブラリとともに、itertools本質的に悪いスタイルです。GvRが実際にはひどい間違いか、単にパフォーマンスの問題だと言っていない限り、「Pythonicness」が言っていることは、愚かであることを忘れることだけが自然な結論です;-)
Steve Jessop

4
@SteveJessop:実際、Guidoはmap/ filterを削除することはPython 3にとって素晴らしいアイデアであると考え、他のPythonistaによる反抗のみが組み込みの名前空間にそれらを保持しました(その間、reduceに移動しましたfunctools)。私は個人的に反対する(mapfilterされ罰金を事前に定義して、特に組み込み関数、場合ちょうどそれらを使用することはありませんlambda必要とされるであろう)が、GVRは基本的に年間のないPython的にそれらを呼びかけています。
ShadowRanger、2015年

@ShadowRanger:trueですが、GvRは削除する予定でしたitertoolsか?この回答から引用する部分は、私を困惑させる主な主張です。私は彼の理想的な世界にいるかどうか、mapそして(または)にfilter移動するか完全に行くかはわかりませんが、どちらにしても、それが全体としてPythonicでないと言うと、「Pythonic」が何であるか本当にわかりません意味するはずですが、「GvRが人々に使用を推奨するもの」に似ているとは思いません。itertoolsfunctoolsitertools
Steve Jessop

2
@SteveJessop:map/ filterではなく、のみをアドレス指定していましたitertools。関数型プログラミングは完全にPython的である(itertoolsfunctoolsそしてoperatorすべての心の中で関数型プログラミングと特異的に設計された、と私はすべての時間Pythonで機能的なイディオムを使用して)、そしてitertools自分自身を実装するための痛みだろうな機能を提供し、それが特異的だmapfilterジェネレータ式で冗長化されてグイドが彼らを憎んだ。itertoolsいつも元気でした。
ShadowRanger、2015年

1
方法があったら、私はこの答えを好きになることができました。よく説明しました。
NelsonGon

95

Python 2:を使用する必要がmapありますfilter代わりに、リスト内包の。

それらが「Pythonic」ではないにもかかわらずそれらを好むはずの客観的な理由はこれです:
それらは引数として関数/ラムダを必要とし、新しいスコープ導入します

私はこれに何度も噛まれました:

for x, y in somePoints:
    # (several lines of code here)
    squared = [x ** 2 for x in numbers]
    # Oops, x was silently overwritten!

しかし、代わりに私が言っていた場合:

for x, y in somePoints:
    # (several lines of code here)
    squared = map(lambda x: x ** 2, numbers)

その後、すべてが順調だったでしょう。

同じスコープで同じ変数名を使用するのは愚かだったと言えます。

私はそうではなかった。コードは元々問題ありxませんでした-2 つのは同じスコープ内にありませんでした。
私は後にそれだけだっ移動しました内部ブロックをコードの別のセクションたで初めて問題が発生し(読み取り:開発中ではなく、メンテナンス中の問題)、それを予期していませんでした。

はい、この間違いを犯さないのあれば、リスト内包はよりエレガントです。
しかし、個人的な経験から(そして他の人が同じ間違いを犯すのを見て)、これらのバグがコードに忍び込んだときに経験する必要のある苦痛に値しないと思うほど十分に起こることを私は見てきました。

結論:

とを使用mapfilterます。それらは、診断が困難なスコープ関連の微妙なバグを防ぎます。

サイドノート:

使用することを検討することを忘れてはいけないimapifilter(でitertools、彼らはあなたの状況に適している場合)!


7
これを指摘してくれてありがとう。リスト内包表記が同じスコープ内にあり、問題である可能性があることは、私には明示的に発生していませんでした。そうは言っても、他のいくつかの回答では、ほとんどの場合、リスト内包表記がデフォルトのアプローチでなければならないことは明らかですが、これは覚えておくべきことです。これは、関数(つまりスコープ)を小さく保ち、完全な単体テストを実行し、assertステートメントを使用することを示す一般的な注意事項でもあります。
TimothyAWiseman

13
@wim:これはPython 2についてのみでしたが、下位互換性を維持したい場合はPython 3に適用されます。私はそれについて知っていて、私はしばらくの間Pythonを使用していました(そうです、ほんの数か月以上)が、それは私に起こりました。私より賢い人が同じ罠に陥るのを見てきました。あなたがとても明るくて、そして/または、これがあなたにとって問題ではない経験をしているなら、私はあなたのために幸せです、私はほとんどの人があなたのようではないと思います。彼らがいた場合は、Pythonの3でそれを修正するために、このような衝動がないだろう
user541686

12
申し訳ありませんが、これは2012年後半にpython 3が登場した直後に書いたものです。答えは、カット中にバグに噛まれただけで人気のないpythonコーディングスタイルを推奨しているようです。コードを貼り付けます。私は明るくて経験豊富であると主張したことはありません。大胆な主張があなたの理由によって正当化されることには同意しません。
WIM

8
@wim:えっ?Python 2は今でも多くの場所で使用されていますが、Python 3が存在するという事実はそれを変えません。そして、「数か月以上Pythonを使用したことがある人にとっては、それはまったく微妙なバグではない」と言った場合、その文は文字通り「経験の浅い開発者のみに関係する」ことを意味します(明らかにあなたではありません)。そして記録のために、私はコードをコピーするのではなく、移動することを太字で言ったので、あなたは明らかに答えを読んでいませんでした。コピーペーストのバグは、言語間でかなり統一されています。この種のバグは、そのスコープのため、Pythonに固有のものです。それは微妙であり、忘れたり見逃したりするのが簡単です。
user541686

3
それでも、mapやに切り替える論理的な理由にはなりませんfilter。どちらかといえば、問題を回避するための最も直接的で論理的な変換は、JeromeJがすでに指摘しているように、リークしないmap(lambda x: x ** 2, numbers)ジェネレーター式にlist(x ** 2 for x in numbers)ではなく、むしろそうすることです。Mehrdadを見てください。個人的には反対票を投じないでください。ここでのあなたの推論に強く反対します。
ウィム

46

実際、mapリスト内包表記は、Python 3言語ではまったく異なる動作をします。次のPython 3プログラムを見てください。

def square(x):
    return x*x
squares = map(square, [1, 2, 3])
print(list(squares))
print(list(squares))

「[1、4、9]」という行を2回出力することを期待するかもしれませんが、代わりに「[1、4、9]」の後に「[]」を出力します。初めて見るときsquaresと3つの要素のシーケンスとして動作するようにが、2回目は空の要素として動作します。

Python 2言語でmapは、リスト内包表記が両方の言語で行うように、プレーンな古いリストを返します。重要なのはmap、Python 3(およびimapPython 2)の戻り値がリストではないことです。これはイテレータです!

要素は、リストを反復する場合とは異なり、反復子を反復するときに消費されます。これがsquares、最後のprint(list(squares))行で空に見える理由です。

要約する:

  • イテレータを扱うときは、イテレータがステートフルであり、トラバースするときに変化することを覚えておく必要があります。
  • リストは、明示的に変更した場合にのみ変更されるため、より予測可能です。彼らはコンテナです。
  • そしておまけ:数字、文字列、タプルはまったく変更できないため、さらに予測可能です。それらは値です

これは、おそらくリスト内包表記の最良の議論です。pythonsマップは、機能マップではなく、機能実装の不自由な赤毛の継子です。私は理解力が本当に嫌いなので、とても悲しいです。
セミオマント2017年

@semiomantレイジーマップ(python3のような)は、熱心なマップ(python2のような)よりも「機能的」だと思います。たとえば、Haskellのマップは遅延します(まあ、Haskellのすべてが遅延します...)。とにかく、マップの連鎖にはレイジーマップの方が適しています。マップに適用されたマップにマップが適用されている場合、python2では中間マップ呼び出しごとにリストが作成されますが、python3では結果リストが1つしかないため、メモリ効率が向上します。
MnZrK 2017年

mapイテレータではなく、データ構造を生成するために必要なものだと思います。しかし、遅延イテレータは遅延データ構造よりも簡単です。思考の糧。ありがとう@MnZrK
セミオマント

mapはイテレータではなくイテラブルを返すと言いたいでしょう。
user541686

16

一般的に、リスト内包表記は、私がやろうとしていることよりも表現力があります。mapどちらもそれを成し遂げますが、前者は、複雑なlambda式である可能性があるものを理解しようとする精神的な負担を軽減します。

また、Guidoがlambdasと関数型関数をPythonへの受け入れについて最も後悔しているものとしてリストしている場所にもインタビューがあります(そうすることで、それらが非Pythonicであると主張することができます)その。


9
ため息、ええ、彼は私のスタウトサポートにもかかわらず、戻ってそれに行ってきましたので、Pythonの3に完全にラムダを除去するためのグイドの本来の意図は、それに対するロビー活動の集中砲火を得た-よくああ、推測ラムダのはあまりにも便利な、多くの中SIMPLEの例のみを問題は、それがSIMPLEの境界を超えるか、名前に割り当てられる場合です(後者の場合、defの愚かなホブリングの複製です!-)。
Alex Martelli、

1
あなたが考えているインタビューはこれです:amk.ca/python/writing/gvr-interview、ここでGuidoは次のように語っています。ラムダ関数などの関数型プログラミング機能の一部。ラムダは、小さな無名関数を作成できるキーワードです。map、filter、reduceなどの組み込み関数は、リストなどのシーケンス型に対して関数を実行します。 」
J.テイラー

3
@アレックス、あなたの長年の経験はありませんが、ラムダよりも複雑なリストの理解がはるかに複雑です。もちろん、言語機能の悪用は常に抵抗するのが難しい誘惑です。リストの内包表記がラムダよりも(経験的に)乱用されやすいのは興味深いことですが、なぜそうなるのかはわかりません。また、「ホブリング」が必ずしも悪いことではないことも指摘しておきます。「この行が行っているかもしれないこと」の範囲を縮小すると、読者にとってそれが簡単になる場合があります。たとえばconst、C ++ のキーワードは、これらの行に沿って大きな勝利です。
Stuart Berg

> guido。これは、グイドが彼の頭の中にないことのもう1つの証拠です。もちろん、lambdaは非常に不完全なものになっているので(ステートメントはありません。)使用するのが難しく、とにかく制限されています。
javadba

16

考えられるケースは次のとおりです。

map(lambda op1,op2: op1*op2, list1, list2)

対:

[op1*op2 for op1,op2 in zip(list1,list2)]

マップの代わりにリスト内包表記を使用したい場合は、zip()が不幸で不必要なオーバーヘッドになるのではないかと思います。誰かが肯定的にも否定的にもこれを明確にすれば素晴らしいでしょう。


"[op1 * op2 from op1、op2 in zip(list1、list2)]]" | s / form / for /そして、zipなしの同等のリスト:(判読不能)[list1 [i] * list2 [i] for i in range(len(list1))]
弱めの

2
2番目のコード引用、@ andz、および@weakishのコメントでも、「from」ではなく「for」である必要があります。理解をリストするための新しい構文アプローチを発見したと思いました...ダーン。
physicsmichael

4
非常に遅いコメントを追加するには、次をzip使用して遅延させることができますitertools.izip
tacaswell

5
私はまだ好きだと思いますmap(operator.mul, list1, list2)。理解が不器用になるのは、これらの非常に単純な左側の式にあります。
Yann Vernier 2015年

1
mapがその関数の入力としていくつかのイテラブルを受け取り、zipを回避できることに気づきませんでした。
bli

16

非同期、並列、または分散コードを作成する場合は、おそらくmapリストの理解よりも優先されます。ほとんどの非同期、並列、または分散パッケージは、mapPythonをオーバーロードする関数を提供するためmapです。次に、適切なmap関数をコードの残りの部分に渡すことにより、元のシリアルコードを変更して並列実行する必要がなくなる場合があります(など)。



1
Pythonのマルチプロセッシングモジュールはこれを行います: docs.python.org/2/library/multiprocessing.html
Robert L.

9

したがって、Python 3 map()はイテレーターなので、必要なもの、つまりイテレーターまたはlistオブジェクトを覚えておく必要があります。

@AlexMartelliはすでに述べたように、関数をmap()使用しない場合にのみ、リスト内包よりも高速ですlambda

いくつかの時間比較を紹介します。

Python 3.5.2とCPython
私はJupiterノートブックと特に%timeit組み込みのマジックコマンドを使用しました
測定:s == 1000 ms == 1000 * 1000 µs = 1000 * 1000 * 1000 ns

セットアップ:

x_list = [(i, i+1, i+2, i*2, i-9) for i in range(1000)]
i_list = list(range(1000))

組み込み関数:

%timeit map(sum, x_list)  # creating iterator object
# Output: The slowest run took 9.91 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 277 ns per loop

%timeit list(map(sum, x_list))  # creating list with map
# Output: 1000 loops, best of 3: 214 µs per loop

%timeit [sum(x) for x in x_list]  # creating list with list comprehension
# Output: 1000 loops, best of 3: 290 µs per loop

lambda 関数:

%timeit map(lambda i: i+1, i_list)
# Output: The slowest run took 8.64 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 325 ns per loop

%timeit list(map(lambda i: i+1, i_list))
# Output: 1000 loops, best of 3: 183 µs per loop

%timeit [i+1 for i in i_list]
# Output: 10000 loops, best of 3: 84.2 µs per loop

ジェネレータ式などもあります。PEP-0289を参照してください。比較に追加すると便利だと思いました

%timeit (sum(i) for i in x_list)
# Output: The slowest run took 6.66 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 495 ns per loop

%timeit list((sum(x) for x in x_list))
# Output: 1000 loops, best of 3: 319 µs per loop

%timeit (i+1 for i in i_list)
# Output: The slowest run took 6.83 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 506 ns per loop

%timeit list((i+1 for i in i_list))
# Output: 10000 loops, best of 3: 125 µs per loop

listオブジェクトが必要です:

カスタム関数の場合はリスト内包表記を使用しlist(map())、組み込み関数がある場合は使用します

listオブジェクトは必要ありません。反復可能なオブジェクトが必要です。

常に使用してmap()ください!


1

オブジェクトのメソッドを呼び出すための3つのメソッドを比較する簡単なテストを実行しました。この場合、時間差はごくわずかであり、問​​題の関数の問題です(@Alex Martelliの応答を参照)。ここでは、次のメソッドを確認しました。

# map_lambda
list(map(lambda x: x.add(), vals))

# map_operator
from operator import methodcaller
list(map(methodcaller("add"), vals))

# map_comprehension
[x.add() for x in vals]

リストのサイズを増やすためvalsに、整数(Python int)と浮動小数点数(Python float)の両方の(変数に格納された)リストを調べました。次のダミークラスDummyNumが考慮されます。

class DummyNum(object):
    """Dummy class"""
    __slots__ = 'n',

    def __init__(self, n):
        self.n = n

    def add(self):
        self.n += 5

具体的には、addメソッド。__slots__属性には、メモリサイズを縮小、クラス(属性)によって必要な合計メモリを定義するためのPythonでシンプルな最適化です。これが結果のプロットです。

Pythonオブジェクトメソッドのマッピングのパフォーマンス

前に述べたように、使用される手法は最小限の違いを生むため、最も読みやすい方法で、または特定の状況でコーディングする必要があります。この場合、map_comprehension特にリストが短い場合、オブジェクト内の両方のタイプの追加で、リストの理解(手法)が最も速くなります。

プロットとデータの生成に使用したソースについては、このペーストビンにアクセスしてください。


1
すでに他の回答で説明されているmapように、関数がまったく同じ方法で呼び出された場合にのみ高速になります(つまり、[*map(f, vals)]vs [f(x) for x in vals])。したがってlist(map(methodcaller("add"), vals))、よりも高速です[methodcaller("add")(x) for x in vals]mapループする相手がいくつかのオーバーヘッドを回避できる別の呼び出しメソッドを使用する場合(たとえばx.add()methodcallerまたはラムダ式のオーバーヘッドを回避する場合)は、高速にならない可能性があります。この特定のテストケースで[*map(DummyNum.add, vals)]は、より高速になります(基本的に同じパフォーマンスDummyNum.add(x)x.add()持っているため)。
GZ0

1
ちなみに、明示的なlist()呼び出しはリスト内包表記よりも少し遅くなります。公正な比較のために、あなたは書く必要があります[*map(...)]
GZ0

@ GZ0素晴らしいフィードバックをありがとう!すべてが理にかなっており、list()呼び出しがオーバーヘッドの増加に気づいていませんでした。答えを読むのにより多くの時間を費やしていたはずです。公平な比較のためにこれらのテストを再実行しますが、違いは無視できるかもしれません。
craymichael

0

最もPython的な方法は、mapとの代わりにリスト内包表記を使用することだと思いfilterます。その理由は、リストの内包表記がmapおよびおよびよりも明確であるためですfilter

In [1]: odd_cubes = [x ** 3 for x in range(10) if x % 2 == 1] # using a list comprehension

In [2]: odd_cubes_alt = list(map(lambda x: x ** 3, filter(lambda x: x % 2 == 1, range(10)))) # using map and filter

In [3]: odd_cubes == odd_cubes_alt
Out[3]: True

ご覧のとおり、内包lambda表記はmap必要に応じて追加の式を必要としません。さらに、理解はフィルタリングを容易にmapするfilterことを可能にしますが、フィルタリングを許可することを要求します。


0

@ alex-martelliでコードを試してみましたが、矛盾が見つかりました

python -mtimeit -s "xs=range(123456)" "map(hex, xs)"
1000000 loops, best of 5: 218 nsec per loop
python -mtimeit -s "xs=range(123456)" "[hex(x) for x in xs]"
10 loops, best of 5: 19.4 msec per loop

私のコードから明らかなように、マップは非常に大きな範囲でも同じ時間かかりますが、リスト内包表記を使用すると多くの時間がかかります。「unpythonic」と見なされることを除いて、マップの使用に関連するパフォーマンスの問題に直面したことはありません。


3
これは非常に古い質問であり、あなたが参照している答えは、おそらくmapリストを返すPython 2に関連して書かれたものと思われます。Python 3ではmap遅延評価されるため、単純に呼び出しを行っmapても新しいリスト要素は計算されないため、このような短い時間が得られるのはなぜですか。
kaya3

私はあなたがPython 3.xを使用していると思います。この質問をしたとき、Python 3は最近リリースされたばかりで、Python 2.xが非常に標準でした。
TimothyAWiseman
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.