読者です 読者をやめる 読者になる 読者になる

座敷牢日誌

都落ちした元SEがソフトウェアやネット関連のことを書いています

Python2の map/filter 関数とリスト内包表記のパフォーマンス比較

python 2.x での話。pythonでちょっとした仕事のためにスクリプトを書捨てするとき、 よくリスト内包表記で書く。リストのようなシーケンシャルなデータを処理するために、

age_list = []
for person in people:
    if person.is_alive:
        age_list.append(person.age)

こんな感じで書いたりすると思うが、リスト内包記法では、次のように書ける。

age_list = [person.age for person in people]

戻り値はあってもなくてもいい。

[person.go_office() for person in people]

for文で回すよりリスト内包表記のほうが速い。

性能云々というより横着してリスト内包表記を使うことが多いんだが、同じようなことが できる mapfilter といった組み込み関数とくらべてどうなのか調べてみた。

vs map関数

map関数 vs リスト内包表記。試したコードとその結果。

# coding: utf8

import timeit

def power3x(m):
    return m ** 3

setup_stmt = 'from __main__ import power3x'
print u'map関数', timeit.timeit('map(power3x, range(100))', setup = setup_stmt)
print u'リスト内包表記', timeit.timeit('[i ** 3 for i in range(100)]')
print u'リスト内包表記 + 関数呼び出し', timeit.timeit('[power3x(i) for i in range(100)]', setup = setup_stmt)

u""" result
map関数 21.4547314202
リスト内包表記 10.9839792804
リスト内包表記 + 関数呼び出し 22.288744181
"""

リスト内包表記のほうが速いが、各要素に対して関数を呼ぶようにするとあまり変わらな くなってしまう。

vs filter 関数

次にvs filter関数。偶数かどうかを判定して返すようにしている。

# coding: utf8

import timeit

def is_even(n):
    return n % 2 == 0

setup_stmt = 'from __main__ import is_even'

print u'filter関数', timeit.timeit('filter(is_even, range(100))', setup = setup_stmt)
print u'リスト内包表記', timeit.timeit('[i for i in range(100) if i % 2 == 0]')
print u'リスト内包表記 + 関数呼び出し', timeit.timeit('[i for i in range(100) if is_even(i)]', setup = setup_stmt)

u""" result
filter関数 17.997468681
リスト内包表記 9.1965228118
リスト内包表記 + 関数呼び出し 22.3147228619
"""

やはりリスト内包表記が速いが、関数呼び出しを組み合わせるとmap関数と同じように極 端に遅くなってしまう。

リファレンスの解説

python2.7のリファレンスには、次のように書いてあった。 5. データ構造 ? Python 2.7ja1 documentation

リストの内包表記 (list comprehension) は、 map() や filter() や lambda を使わ ずにリストを生成するための簡潔な方法を提供しています。内包表記によるリストの定 義は、たいてい map(), filter(), lambda を使ってリストを生成するコードよりも明 快になります。

このようにも書いてある。

実際には複雑な流れの式よりも組み込み関数を使う方が良いです。

python3.xの場合

ちなみにpython3.3のドキュメントでは、次のように書かれていた。 5. データ構造 ? Python 3.3.3 ドキュメント

リスト内包表記はリストを生成する簡潔な手段を提供しています。主な利用場面は、あ るシーケンスや iterable (イテレート可能オブジェクト) のそれぞれの要素に対して ある操作を行った結果を要素にしたリストを作ったり、ある条件を満たす要素だけから なる部分シーケンスを作成することです。

複雑なら組込み関数を使えという話は、同じように書かれている。ただ、Python2.7の map, filter がリストを返すのに対して、Python3.3は map オブジェクト, filter オブジェクト (イテレータ) を返すようになっているから、一概に比較できないのかも。

map(lambda n: n, range(10)) == [n for n in range(10)] これがPython2だと真と評価されるが 3なら偽が返される。

多くの場合はリスト内包表記のほうが速い

とりたてて mapfilter のほうが速いわけではないようなので、 必要があればリスト内包表記以外の方法を採る、程度でよさそうかなと思いました。

広告を非表示にする