【Python】 内包表記を使ったリストの生成方法

内包表記は、リストを生成するPythonのコードの書き方の一つです。
同じ機能はforループの構文やmap()を使って書けますが、コードの可読性や実行速度の点で内包表記の方が有利であると言われています。

本記事では、まずはforループ、map()関数を使ったリストの生成方法をおさらいした後に、内包表記の基本について述べ、その後、コードの簡潔さと処理速度を比較してみます。
# 2020/2/3 記事更新

確認した環境

  • OS: Ubuntu 16.04LTS
  • Python3.7.4

リストの生成

本記事では、リストを生成する方法として以下3つについて考えます。

以降の章では、簡単な例として以下のリストを生成するコードを考えます。

a = [0, 2, 4, 6, 8]

forループを使ったリストの生成

素直に書く場合は、forループを使うことが多いと思います。
最初に準備した空のリストに、forループで逐次値を追加していきます。

>>> a = []
>>> for i in range(5):
...     a.append(i * 2)
... 

# 結果
>>> a
[0, 2, 4, 6, 8]

map()を使ったリストの生成

map()はイテレータオブジェクトを生成するのでリスト型にするにはlist()と併用する必要があります。

>>> a = list(map(lambda x : 2*x, range(5)))

# 結果
>>> a
[0, 2, 4, 6, 8]

参考)[Python] リストの各要素のデータ型を変換する:map関数の使いかた

内包表記を使ったリストの生成

リスト内包表記は、リストから新しいリストを作るための簡潔な構文でもあり、下記に示すように括弧[]の中の 式、 for 句(for … in …)、そして0個以上の for か if 句で構成されます。

[ 式 for ... in ...(0個以上の for か if 句)]

簡単な例を以下に示します。

>>> a = [2*n for n in range(5)]

# 結果
>>> a
[0, 2, 4, 6, 8]

内包表記のメリット

  • lambda式を使わないので、上記のmap()を使うよりも簡単に書ける
  • if句を使うことで、入力リストから簡単に要素を抜き出すことができる(次項参照)
  • 実は辞書集合も内包表記を使うことができる
  • [辞書内包表記]
    keyとvalueの要素を組み合わせた上で”{ }”で囲います。

    >>> fruits = ['apple', 'orange', 'grape']
    >>> num = [4, 5, 6]
    >>> {i : j for i, j in zip(fruits, num)}
    {'apple': 4, 'grape': 6, 'orange': 5}

    [集合内包表記]
    集合(set)型を表す”{ }”で囲っています。

    >>> a = {2*n for n in range(5)}
    >>> a
    {0, 2, 4, 6, 8}
    
    >>> type(a)
    

参考)公式リファレンス:5.1.3. リストの内包表記

こちらの書籍も内包表記について判りやすく解説されています。ご参考にどうぞ!
Effective Python ―Pythonプログラムを改良する59項目 
(項目7:mapやfliterの代わりにリスト内包表記を使う)

if文による条件分岐

リスト内包表記は以下のようにif文を入れて条件分岐も表現できます。
後置if文の構造になっていることに注意です。

>>> [n for n in range(10) if n%2 ==0]
[0, 2, 4, 6, 8]

if〜elseによる条件分岐

elseを使う場合は以下のようにif文を前に書きます。

>>> [n if n%2 ==0 else 'odd' for n in range(10)]
[0, 'odd', 2, 'odd', 4, 'odd', 6, 'odd', 8, 'odd']

後置if文と同じように書くとエラーになります。

>>> [n for n in range(10) if n%2 ==0 else 'odd']
  File "", line 1
  [n for n in range(10) if n%2 ==0 else 'odd']
  ^
SyntaxError: invalid syntax

処理速度について

リスト内包表記は実行効率が良いと言われていますので、確認してみました。

以下は、上記3つの方法(forループ、map()、リスト内包表記)の処理時間の比較グラフです。

 ※横軸:データ数/縦軸:処理時間(両軸とも基底10の対数値);(コードはこちら

リスト内包表記 処理速度比較

処理時間の数値自身はPCによって異なるので無視頂くとして、相対的に比較するとリスト内包表記はforループやmap関数を使った場合と比較して約1.6倍速い結果となりました。
理由については、書籍「エキスパートPythonプログラミング改訂2版」に以下のように記載されています。

この書き方はC言語では良いかもしれませんが、次の理由のためPythonでは遅くなります。

  • リストを操作するコードをループごとにインタープリタ上で処理する必要がある
  • カウンタ操作もループごとにインタープリタ上で処理する必要がある
  • append()はリストのメソッドであるため、イテレーションごとに関数ルックアップの追加のコストが必要になる

(中略)リスト内包表記を利用すると、先ほどの構文が行っていた処理の一部がインタープリタ内部で実行されるようになるので、処理が早くなります。
書籍「エキスパートPythonプログラミング改訂2版、アスキードワンゴ、MichalJaworski他著」
(注:「先ほどの構文」というのは、forループを使った記述のこと)

また、こちらのサイト様は内部動作まで踏み込んで詳しい分析をされていますので、ご参考まで。

まとめ

今回は、Pythonでよく使われるリスト内包表記についてまとめました。forループを使うよりもすっきりしたコードになり、処理効率も良いので是非使いこなしていきたいです。

Learn more...

書籍でもう少し詳しく学びたい場合はこちらもどうぞ。筆者もかなり参考にさせてもらっています!