リストの内包表記よりmap()
を使用することを好む理由はありますか、またはその逆です。それらのどちらかが一般的に他より効率的か、または一般的にもっとPythonicであると考えられますか?
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
ケース
map
を使用するには、「unpythonic」と見なされますが。たとえば、map(sum, myLists)
は[sum(x) for x in myLists]
よりエレガントで簡潔です。ただ繰り返すために、2回入力しなければならないダミー変数(sum(x) for x...
、sum(_) for _...
、sum(readableName) for readableName...
など)を作成する必要がないという優雅さが得られます。 filter
とreduce
、そして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"が嫌いです。それにもかかわらず、map
とfilter
、および類似の関数(非常に便利な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
クラスは逆アセンブルに対して少し不透明ですが、スピードテストを使用して適切に作成できます。
map
とfilter
を使うべきです。"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:メンテナンス中の問題) (開発ではなく)私はそれを期待していませんでした。
はい、この間違いをしないなら、リスト内包表記のほうがエレガントです。
しかし、個人的な経験から(そして他の人が同じミスをしているのを見ることから)、私はこれらのバグがあなたのコードに入り込んだときに苦労する価値がないと思うほど十分に起こるのを見ました。
map
とfilter
を使用してください。それらは微妙に診断しにくいスコープ関連のバグを防ぎます。
状況に適していれば、imap
とifilter
(itertools
内)の使用を検討することを忘れないでください。
実際、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
が空に見える理由です。
要約する:
リスト内包表記は一般的に私がやろうとしていることをmap
より表現的に表現しています - 前者は複雑なlambda
式を理解しようとするという精神的な負荷を軽減します。
Guidoがlambda
sと関数関数をPythonへの受け入れについて最も後悔しているものとしてリストしているインタビューもどこかにあります(私はそれをすぐに見つけることはできません)。その。
非同期、並列、または分散コードを書くつもりであれば、おそらくリスト内包表記よりmap
を好むでしょう - ほとんどの非同期、並列、または分散パッケージはpythonのmap
をオーバーロードするためのmap
関数を提供します。次に、適切なmap
関数を残りのコードに渡すことで、元のシリアルコードを修正してそれをパラレルで実行させる必要がない場合があります(など)。
これは1つの可能性のあるケースです:
map(lambda op1,op2: op1*op2, list1, list2)
対:
[op1*op2 for op1,op2 in Zip(list1,list2)]
Zip()は、マップの代わりにリストの内包表記を使用することを主張した場合にふける必要がある、不幸で不必要なオーバーヘッドであると思います。肯定的か否定的かにかかわらず、誰かがこれを明確にしていれば素晴らしいでしょう。
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()
を使う!
オブジェクトのメソッドを呼び出す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の単純な最適化であり、クラス(属性)が必要とする合計メモリを定義して、メモリサイズを削減します。結果のプロットは次のとおりです。
前に述べたように、使用する手法は最小限の違いをもたらすため、最も読みやすい方法で、または特定の状況でコーディングする必要があります。この場合、リスト内包表記(map_comprehension
テクニック)は、特に短いリストの場合、オブジェクトの両方のタイプの追加で最も高速です。
this Pastebin にアクセスして、プロットとデータの生成に使用するソースを探してください。
私は、最もPythonicな方法は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
を必要としますが、内包表記は容易にフィルタリングを可能にします。