web-dev-qa-db-ja.com

リスト内包表記とマップ

リストの内包表記よりmap()を使用することを好む理由はありますか、またはその逆です。それらのどちらかが一般的に他より効率的か、または一般的にもっとPythonicであると考えられますか?

635
TimothyAWiseman

mapは場合によっては微視的に速いかもしれません(目的のためにラムダを作っているのではなく、mapとlistcompで同じ関数を使っているとき)。リスト内包表記は他の場合にはより速いかもしれません、そしてほとんどの(すべてではない)pythonistasはそれらをより直接的でより明確に考えます。

まったく同じ関数を使用したときのmapの小さな速度上の利点の例:

$ 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
589
Alex Martelli

ケース

  • 一般的な場合:ほとんどの場合、pythonにリスト内包表記を使用します。あなたがあなたのコードを読む初心者プログラマにあなたがしていることがもっと明白になるでしょう。リストの内包表記は繰り返しのためのpythonの事実上の標準であるため、pythonプログラマーにしていることはさらに明白になります。それらはexpectedです。
  • あまり一般的ではないケースにすでに関数が定義されている場合mapを使用するには、「unpythonic」と見なされますが。たとえば、map(sum, myLists)[sum(x) for x in myLists]よりエレガントで簡潔です。ただ繰り返すために、2回入力しなければならないダミー変数(sum(x) for x...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という言葉が私の目にはいつも優雅であるとは思わないので、私はWord "Pythonic"が嫌いです。それにもかかわらず、mapfilter、および類似の関数(非常に便利なitertoolsモジュールのような)は、スタイルに関してはおそらく非調和的と考えられます。

怠惰

効率の面では、ほとんどの関数型プログラミング構成体と同様に、MAPは遅延する可能性があり、実際は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はそれらを非lazyとして実装することを選択しました。それにもかかわらず、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            ^^^^^^^^^

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

ただし、fを作成したいmapという既製の関数があり、list(...)で直ちに評価を強制することでmapの遅延を無視します。非常に興味深い結果が得られます。

% 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クラスは逆アセンブルに対して少し不透明ですが、スピードテストを使用して適切に作成できます。

417
ninjagecko

リスト内包表記の代わりにmapfilterを使うべきです。

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

私はこれに2回以上噛まれました:

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)

それからすべてが大丈夫だっただろう。

私は、同じスコープ内で同じ変数名を使用することにばかげていると言っていいでしょう。

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

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

結論:

mapfilterを使用してください。それらは微妙に診断しにくいスコープ関連のバグを防ぎます。

サイドノート:

状況に適していれば、imapifilteritertools内)の使用を検討することを忘れないでください。

89
Mehrdad

実際、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は普通の古いリストを返します。肝心なことは、Python 3のmap(そしてPython 2のimap)の戻り値はリストではないということです - それはイテレータです!

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

要約する:

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

リスト内包表記は一般的に私がやろうとしていることをmapより表現的に表現しています - 前者は複雑なlambda式を理解しようとするという精神的な負荷を軽減します。

Guidoがlambdasと関数関数をPythonへの受け入れについて最も後悔しているものとしてリストしているインタビューもどこかにあります(私はそれをすぐに見つけることはできません)。その。

16
Dan

非同期、並列、または分散コードを書くつもりであれば、おそらくリスト内包表記よりmapを好むでしょう - ほとんどの非同期、並列、または分散パッケージはpythonのmapをオーバーロードするためのmap関数を提供します。次に、適切なmap関数を残りのコードに渡すことで、元のシリアルコードを修正してそれをパラレルで実行させる必要がない場合があります(など)。

15
Mike McKerns

これは1つの可能性のあるケースです:

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

対:

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

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

15
Andz

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()を使う!

6
vishes_shell

オブジェクトのメソッドを呼び出す3つのメソッドを比較する簡単なテストを実行しました。この場合の時間差は無視でき、問題の関数の問題です(@Alex Martelliの response を参照)。ここでは、次のメソッドを調べました。

# 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]

リストサイズを増やすために、整数(Python vals)と浮動小数点数(Python int)の両方のリスト(変数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の単純な最適化であり、クラス(属性)が必要とする合計メモリを定義して、メモリサイズを削減します。結果のプロットは次のとおりです。

Performance of mapping Python object methods

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

this Pastebin にアクセスして、プロットとデータの生成に使用するソースを探してください。

0
craymichael

私は、最もPythonicな方法はmapfilterの代わりにリスト内包表記を使うことだと思います。その理由は、リスト内包表記はmapfilterよりも明確だからです。

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
lmiguelvargasf