map()
リスト内包表記を使用すること、またはその逆を好む理由はありますか?それらのどちらかは、一般的に効率的ですか、または一般的に他のものよりパイソンのように見なされますか?
map()
リスト内包表記を使用すること、またはその逆を好む理由はありますか?それらのどちらかは、一般的に効率的ですか、または一般的に他のものよりパイソンのように見なされますか?
回答:
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
map(operator.attrgetter('foo'), objs)
より読みやすい[o.foo for o in objs]
?!
o
ここのような不要な名前を導入しないことを好み、あなたの例はその理由を示しています。
str()
例ではそうです。
ケース
map
、「非Pythonic」と見なされますが、使用するのが妥当な場合がよくあります。たとえば、map(sum, myLists)
はよりエレガント/簡潔です[sum(x) for x in myLists]
。反復するためだけに2回入力する必要があるダミー変数(例:sum(x) for x...
or 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」という言葉が嫌いなのは、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 ^^^^^^^^^
したがって、すべてのデータを使用しない場合、または事前に必要なデータ量がわからない場合、map
python3(および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
クラスは逆アセンブルに対して少し不透明ですが、スピードテストで納得することができます。
map
とそれに従って使用する人にとってそれは公平ではないと思いますfilter
標準ライブラリとともに、itertools
本質的に悪いスタイルです。GvRが実際にはひどい間違いか、単にパフォーマンスの問題だと言っていない限り、「Pythonicness」が言っていることは、愚かであることを忘れることだけが自然な結論です;-)
map
/ filter
を削除することはPython 3にとって素晴らしいアイデアであると考え、他のPythonistaによる反抗のみが組み込みの名前空間にそれらを保持しました(その間、reduce
に移動しましたfunctools
)。私は個人的に反対する(map
とfilter
され罰金を事前に定義して、特に組み込み関数、場合ちょうどそれらを使用することはありませんlambda
必要とされるであろう)が、GVRは基本的に年間のないPython的にそれらを呼びかけています。
itertools
か?この回答から引用する部分は、私を困惑させる主な主張です。私は彼の理想的な世界にいるかどうか、map
そして(または)にfilter
移動するか完全に行くかはわかりませんが、どちらにしても、それが全体としてPythonicでないと言うと、「Pythonic」が何であるか本当にわかりません意味するはずですが、「GvRが人々に使用を推奨するもの」に似ているとは思いません。itertools
functools
itertools
map
/ filter
ではなく、のみをアドレス指定していましたitertools
。関数型プログラミングは完全にPython的である(itertools
、functools
そしてoperator
すべての心の中で関数型プログラミングと特異的に設計された、と私はすべての時間Pythonで機能的なイディオムを使用して)、そしてitertools
自分自身を実装するための痛みだろうな機能を提供し、それが特異的だmap
とfilter
ジェネレータ式で冗長化されてグイドが彼らを憎んだ。itertools
いつも元気でした。
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 つのは同じスコープ内にありませんでした。
私は後にそれだけだっ移動しました内部ブロックをコードの別のセクションたで初めて問題が発生し(読み取り:開発中ではなく、メンテナンス中の問題)、それを予期していませんでした。
はい、この間違いを犯さないのであれば、リスト内包はよりエレガントです。
しかし、個人的な経験から(そして他の人が同じ間違いを犯すのを見て)、これらのバグがコードに忍び込んだときに経験する必要のある苦痛に値しないと思うほど十分に起こることを私は見てきました。
とを使用map
しfilter
ます。それらは、診断が困難なスコープ関連の微妙なバグを防ぎます。
使用することを検討することを忘れてはいけないimap
とifilter
(でitertools
、彼らはあなたの状況に適している場合)!
map
やに切り替える論理的な理由にはなりませんfilter
。どちらかといえば、問題を回避するための最も直接的で論理的な変換は、JeromeJがすでに指摘しているように、リークしないmap(lambda x: x ** 2, numbers)
ジェネレーター式にlist(x ** 2 for x in numbers)
ではなく、むしろそうすることです。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
は、リスト内包表記が両方の言語で行うように、プレーンな古いリストを返します。重要なのはmap
、Python 3(およびimap
Python 2)の戻り値がリストではないことです。これはイテレータです!
要素は、リストを反復する場合とは異なり、反復子を反復するときに消費されます。これがsquares
、最後のprint(list(squares))
行で空に見える理由です。
要約する:
map
イテレータではなく、データ構造を生成するために必要なものだと思います。しかし、遅延イテレータは遅延データ構造よりも簡単です。思考の糧。ありがとう@MnZrK
一般的に、リスト内包表記は、私がやろうとしていることよりも表現力があります。map
どちらもそれを成し遂げますが、前者は、複雑なlambda
式である可能性があるものを理解しようとする精神的な負担を軽減します。
また、Guidoがlambda
sと関数型関数をPythonへの受け入れについて最も後悔しているものとしてリストしている場所にもインタビューがあります(そうすることで、それらが非Pythonicであると主張することができます)その。
const
、C ++ のキーワードは、これらの行に沿って大きな勝利です。
lambda
は非常に不完全なものになっているので(ステートメントはありません。)使用するのが難しく、とにかく制限されています。
考えられるケースは次のとおりです。
map(lambda op1,op2: op1*op2, list1, list2)
対:
[op1*op2 for op1,op2 in zip(list1,list2)]
マップの代わりにリスト内包表記を使用したい場合は、zip()が不幸で不必要なオーバーヘッドになるのではないかと思います。誰かが肯定的にも否定的にもこれを明確にすれば素晴らしいでしょう。
zip
使用して遅延させることができますitertools.izip
map(operator.mul, list1, list2)
。理解が不器用になるのは、これらの非常に単純な左側の式にあります。
非同期、並列、または分散コードを作成する場合は、おそらくmap
リストの理解よりも優先されます。ほとんどの非同期、並列、または分散パッケージは、map
Pythonをオーバーロードする関数を提供するためmap
です。次に、適切なmap
関数をコードの残りの部分に渡すことにより、元のシリアルコードを変更して並列実行する必要がなくなる場合があります(など)。
したがって、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の応答を参照)。ここでは、次のメソッドを確認しました。
# 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でシンプルな最適化です。これが結果のプロットです。
前に述べたように、使用される手法は最小限の違いを生むため、最も読みやすい方法で、または特定の状況でコーディングする必要があります。この場合、map_comprehension
特にリストが短い場合、オブジェクト内の両方のタイプの追加で、リストの理解(手法)が最も速くなります。
プロットとデータの生成に使用したソースについては、このペーストビンにアクセスしてください。
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()
持っているため)。
list()
呼び出しはリスト内包表記よりも少し遅くなります。公正な比較のために、あなたは書く必要があります[*map(...)]
。
list()
呼び出しがオーバーヘッドの増加に気づいていませんでした。答えを読むのにより多くの時間を費やしていたはずです。公平な比較のためにこれらのテストを再実行しますが、違いは無視できるかもしれません。
最も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
ことを可能にしますが、フィルタリングを許可することを要求します。
@ 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」と見なされることを除いて、マップの使用に関連するパフォーマンスの問題に直面したことはありません。
map
リストを返すPython 2に関連して書かれたものと思われます。Python 3ではmap
遅延評価されるため、単純に呼び出しを行っmap
ても新しいリスト要素は計算されないため、このような短い時間が得られるのはなぜですか。