ぬらくら日記

ぬらりくらりと過ごすプログラマが書く徒然日記。

python3はリスト内包表記とmapどっちがいいか?

おはようございます、ぬらくらです。

昨日、友達とこんな話をしていました。

僕「リスト内包表記って結局写像じゃん? mapと同じだよね」

友「じゃあ何が違うん?」

ということで試してみました!

文法

まず、文法ではどのような差があるでしょうか?

とりあえず、0~999を2乗するプログラムを書いてみます!

リスト内包表記
[i**2 for i in range(100)]
map
map(lambda i:i**2,range(100))

書き方は全然違いますね。

特に、map型は第一引数が関数であるため、lambda式を用いている点が違います。

速度比較

次に、上記の2コードの速度比較をしてみましょう。

リスト内包表記
In [16]: %timeit [i**2 for i in range(100)]
19.9 µs ± 311 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
map
In [17]: %timeit map(lambda i: i**2, range(1000))
295 ns ± 2.46 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

この場合、mapのほうが早いですね。

ではmapのほうがいいのでしょうか?

いえ、根本的に大きな違いがあります!

mapはmapオブジェクト、リスト内包表記はリストオブジェクト

これはいったいどういうことでしょうか?

実は,map型は宣言された時点では実行されていない、mapオブジェクトとなっています。

ipythonで確認すると、

In [14]: map(lambda i: i**2, range(1000000))
Out[14]: <map at 0x7fe661fa9518>

となっており実行されていません。

これの最大のメリットは、メモリを確保しないことです。

逐次実行するため、一括で大規模なメモリを確保しないのがメリットです。

ということで、 - リストを表現したい場合はリスト内包表記 - イテレータを作りたい場合はmap

といったような使い分けが適切なのではと思います!

おまけ

mapをリストにキャストした場合の時間を図ってみました

In [19]: %timeit list(map(lambda i: i**2, range(100)))
24.6 µs ± 169 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

リスト内包表記よりも時間がかかりますね。 逆にリストにpushする形だと

In [30]: %%timeit
    ...: a = []
    ...: for i in range(100):
    ...:     a.append(i)
    ...:
4.62 µs ± 28.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

え、最速?!

途中から最適化がかかって、繰り返しの場合は爆速になってる…??

理由がわかる方がいたら教えてください…