周回遅れでIT業界デビューしたプロジェクトマネージャーのブログ

就職氷河期にモロにぶち当たり、人生で混迷を極めた末にIT業界に安寧を見出そうとしているアラフォーの活動日記です。

Pythonの内包表記を覚えよう ~入門編~

こんにちは。今日も寒い!

Python使いにはどえらいニュースが舞い込んできました

www.publickey1.jp

もうVBA全部やめちゃってPythonに切り替えてくれてもいいんだよ……!

内包表記のお話

f:id:sionff:20180119162019j:plain

閑話休題Pythonの内包表記です。

はじめPythonを勉強していた時、[]で括ってあってナニコレ? と思ったら、処理時間は短くなるしコードはシンプルになるしでいいことずくめなので覚えよう! と思った次第。

例えばこんなコード

[ counter for counter in iterator ]

こんな式を表現したいとき。xは数字を羅列したリストです。

f:id:sionff:20171219165540p:plain

こちらはfor文。

def sum_of_squares_b(x):
    ans = 0
    for x_i in x:
        ans = ans + x_i * x_i
    return ans

こちらは内包表記。

def sum_of_squares(x):
    return sum([ (x_i * x_i) for x_i in x ])

両方とも、[ counter_0, counter_1, ...... , counter_n ]のリストが生成された後、全部を合算した合計を返します。

やってることは一緒。
でも、for文で回すより内包表記で回した方がコードも処理時間も短くなります。

[ do_a if a > b else do_b ]

ちょうどkaggleのために書いたコードがあったので、時間計測もくっつけました。
300万行くらいのデータを回した時のやつ。

こちらはfor文+if文。

import time
start = time.time()

lower = []
for i in range(len(df)): 
    if df["unit_sales1"][i] < df["unit_sales2"][i]:
        lower.append(df["unit_sales1"][i])
    else:
        lower.append(df["unit_sales2"][i])

print(int(time.time() - start), "秒")

こちらは内包表記。

import time
start = time.time()

lower = [ [ df["unit_sales1"][i] if df["unit_sales1"][i] < df["unit_sales2"][i] else df["unit_sales2"][i] ] for i in range(len(df)) ]

print(int(time.time() - start), "秒")

pandasのDataFrameを使っていて、かつif文つっこんだので内包表記が二重になり。可読性落ちてる……? でもやってることは一緒。

どうして速いの?

検索したらちょうどいい説明があったので引用。

訳注:リストに要素を append() する場合、インタプリタは「リストから append 属性を取り出してそれを関数として呼び出す」という処理をしなければなりません。 それに対して、リスト内包表記を使うと、インタプリタに直接「リストに要素を追加する」という処理をさせることができます。インタプリタが解釈する命令数が減る、属性の取り出しが不要になる、関数呼び出しが不要になる、という3つの理由で、リスト内包表記を使うと速くなります。

dsas.blog.klab.org

  • for文で処理する

 →「Python側で処理するがためにいちいち受け渡しが発生するから遅い」

  • 内包表記で処理する

 →「C側でずっと処理するから受け渡しせずに済んで速い」

とざっくり理解することにしました。

もっと内包表記について知りたかったら

内包表記のバリエーション

qiita.com

内包表記の速度

qiita.com


最初のうちはfor文で書いてから、それを直して内包表記に置き換えてみるといいと思います。私もそうして覚えました。

慣れてくると書くのが楽しくなってきます。
よい内包表記ライフを!