コードがシンプルに!Pythonの内包表記の使い方を解説
この記事では、Pythonでリスト生成をする場合のコードを簡略化できる「内包表記」の使い方を解説します。内包表記を使うことで、コードを短く・シンプルにできるだけでなく、読みやすさも向上させることが可能です。この記事を読んで、使い方をぜひマスターしましょう。
内法表記とは
Pythonには、内包表記という記述方法があります。内包表記を利用すれば、新しいリストの生成を、シンプルなコードで記述することが可能です。また、リストだけでなく集合(set)や辞書(dict)も内包表記を使って生成することが可能です。
他の言語を使っている方にはあまり馴染みがない表記方法かもしれませんが、慣れればかなり便利ですし、コードがシンプルになり、可読性も向上するという優れものです。
それほどたくさんの言語を使ったことはありませんが、RubyやJavaScriptも内包表記に近い記述を行うことが可能です。
内包表記を利用したリストの生成
基本的な使い方
連番値のリスト生成する
内包表記の基本的なパターンは、for
文を使ってリストを生成するパターンです。
以下の例は、連番リストを生成するもので、for
を使って0~9
までの連番を生成しています。
a = [x for x in range(10)]
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
要素数nのリストを特定値で初期化
先ほどの例を少し書き換えると、特定の値で初期化することも可能です。これも、リストの初期化で多用します。
以下の例は、for
文を使って-1
が10個のリストを生成しています。
a = [-1 for _ in range(10)]
# [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1]
条件付きの内法表記
内包表記では、for
に条件式を組み合わせることも可能です。以下、条件式を使った例になります。
if文を用いた条件式で値を設定
偶数の場合だけリストに追加する例です(for
では0~9
まで繰り返していますが、奇数はリストに追加されません)。
for
の後ろに条件を記述します。
a = [x for x in range(10) if x%2 == 0]
# [0, 2, 4, 6, 8]
奇数の場合は0、偶数の場合は1をリストに追加する例です(奇数でも偶数でも値を追加しています)。
for
の前に条件を記述します。
a = [1 if x%2 == 0 else 0 for x in range(10)]
# [1, 0, 1, 0, 1, 0, 1, 0, 1, 0]
上の2つのパターンの違いに注意してください。前者は、追加されるのは偶数のみ、後者は全ての場合でリストへの追加は行われます。
複雑な式
もう少し複雑な式を使った内包表記を紹介します。
式を用いてリストを作成
式を利用してリストの値を生成することも可能です。下記の例は一次式$2x + 3$で値を生成しています。
a = [2 * x + 3 for x in range(10)]
# [3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
関数を用いてリストを作成
内包表記内で関数を呼び出すことも可能です。
下記は関数を利用してリストを生成する例です。
def f(a) :
return a**2
a = [f(x) for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
応用例
内包表記を使った応用例をいくつか紹介します。個人的は、このような場合に内包表記をよく利用しています。
逆引き辞書を作成
辞書の生成にも内包表記を利用することが可能です。
dic = {x : x**2 for x in range(10)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
内包表記を利用することで、逆引きの辞書を作成することも可能です。
dic
が「xからx**2の値を調べる」ものに対して、reverse_dic
は「x**2の値からxを調べる」辞書になります(逆引き)。x→yの辞書とy→xの辞書を両方用意しておくことは結構ありますので、そういう場合に重宝します。
reverse_dic = {dic[v] : v for v in dic}
# {0: 0, 1: 1, 4: 2, 9: 3, 16: 4, 25: 5, 36: 6, 49: 7, 64: 8, 81: 9}
逆引き辞書の作成は、結構いろいろな時に使いますので覚えておくと良いです。
2次元配列(2次元リスト)の生成
入れ子にすることで、2次元や3次元のリストを作成することも可能です。
下記は、2次元のリストを0に初期化する例です。
a = [[0 for _ in range(3)] for _ in range(5)]
# [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
条件に一致する値だけ抜き出す
3の倍数だけ抜き出してリストにする
別のリストから、特定条件に一致する列だけ抜き出して新しいリストを生成することもできます。下記はリストa
から3の倍数の値だけ抜き出してリストb
を作成しています。
import random
a = [random.randint(1,1000) for _ in range(10)]
# [632, 252, 271, 303, 905, 784, 153, 722, 392, 331]
b = [e for e in a if e%3 == 0]
# [252, 303, 153]
速度評価
内法表記の利点は、シンプルに書けることだけではありません。
ループで記述するのと比較して、高速である点もメリットの1つです。
ここでは、実際にどの程度高速なのかを測定した結果を紹介します。
プログラム
評価に利用したプログラムは以下になります。
100万個の要素を持つリストを、内包表記と、ループの2種類の方法で生成しています。
プログラムでは生成処理をloop=100
回繰り返し、処理時間を計測しました。
import time
n = 1000000
loop = 100
# 内包表記を使ったリスト生成
start_time = time.time()
for _ in range(loop):
list_comp = [x for x in range(n)]
end_time = time.time()
comp_time = end_time - start_time
# ループを使ったリスト生成
start_time = time.time()
for _ in range(loop):
list_loop = []
for x in range(n):
list_loop.append(x)
end_time = time.time()
loop_time = end_time - start_time
print(f"内包表記の実行時間: {comp_time}秒")
print(f"ループの実行時間: {loop_time}秒")
結果
結果は以下のようになります。実験に利用した環境はPython3.11.4(Macbook Air m2)です。
内包表記の実行時間: 1.4358179569244385秒
ループの実行時間: 2.668544054031372秒
約1.9倍ほど、内法表記の方が高速という結果になりました。
このように、内包表記を利用することでリストの生成を高速化することが可能です。
書き方だけで約2倍速くなるというのは、結構大きな差です。
まとめ
Pythonの内法表記の記述方法と、応用例、速度についてまとめました。あまり馴染みがない人もいるかもしれませんが、記述がシンプルになることと、速度面で有利なことを考えると、積極的に使っていくと良いかしれません。
個人的には、あまり複雑な内包表記は可読性の面から避けるべきだと思います。内包表記とループのどちらが読みやすいかも考えながらうまく使っていくと良いと思います。