Pythonのリスト内包表記の書き方

内包表記は、速度の点やコードの簡潔さを保つという点でメリットがある非常にPythonらしい書き方と言われています。ここではその中でリスト内包表記の基本的な使い方について詳しく解説します。

Pythonのforループの基本
リスト内包表記を使いこなせるようになるにはfor文を先に理解しておく必要があります。「Pythonのfor文(forループ)の基本」を確認しておきましょう。
目次

1. リスト内包表記

リスト内包表記をよく理解するために、ここではまず、for文を使った通常のリスト作成を見てから、それと同じリストをリスト内包表記で作ります。最後に、それぞれの方法の速度比較を行なっています。

1.1. for文を使った通常のリスト作成

for文を使って新しいリストを作るには、例えば次のように書く必要があります。これは1から5の数値を二乗したリストを作っています。

In [1]:
squares = []
for num in range(1,6):
   squares.append(num**2)
print(squares)
[1, 4, 9, 16, 25]

このように、まず「squares = []」で空のリストを作っておき、その後にfor文で1から5の数値を一つずつ取り出し、「append(num**2)」で、それを二乗した物を一つずつ、空のリストに追加しています。

append()については「Pythonのappendメソッドの使い方」で解説しています。

1.2. リスト内包表記でリスト作成

それでは、これをリスト内包表記で書いてみましょう。リスト内包表記の基本的な書き方は以下の通りです。for文の前に、for文で変数に代入した値を処理する式を書くと覚えれば良いでしょう。

In [ ]:
 for 変数 in イテラブル

リスト内包表記で先ほどと同じリストを作るには次のように書きます。先ほどのコードと比べると、こちらは一行で書くことができて、非常にスッキリとしていますね。

In [2]:
squares = [num**2 for num in range(1, 6)]
print(squares)
[1, 4, 9, 16, 25]

for文の前の式を変えることで、様々な処理をすることができます。以下のコードでは、for文で取り出した数値を三乗しています。

In [3]:
cubes = [num**3 for num in range(1, 6)]
print(cubes)
[1, 8, 27, 64, 125]

最初は慣れないかもしれませんが、何回か使っていると、すぐにしっくりと使いこなせるようになります。

1.3. 速度比較

それでは通常のリスト作成とリスト内包表記の速度比較をしてみましょう。ここでは比較のために10万回のトライアルを行いました。結果、for文の通常のリスト作成では、平均1.65マイクロ秒(標準偏差±66.7ナノ秒)でした。

In [4]:
%%timeit -n 100000
squares = []
for num in range(1,6):
   squares.append(num**2)
1.65 µs ± 66.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

リスト内包表記では平均1.57マイクロ秒(標準偏差65ナノ秒)でした。

In [5]:
%%timeit -n 100000
squares = [num**2 for num in range(1, 6)]
1.57 µs ± 65 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

このことから速度で見るとリスト内包表記の方がわずかに早いですが、顕著な差とは言えません。しかし、リスト内包表記の方が、コードが簡潔で行数が少なくすみます。そのためPythonではリスト内包表記を使う方が良いと言えます。

2. リスト内包表記にif文を含める

次にif文を含めたリスト内包表記を見てみましょう。ここでも、まずはfor文とif文を使った通常のリスト作成を行い、その後に同じリストをリスト内包表記で作成します。そして最後に両者の速度比較を行なっています。

2.1. for文とif文を使った通常のリスト作成

まずは、for文とif文を使った通常のリスト作成を見ていきましょう。以下のコードでは、数値と文字列が含まれているリストから、数値のみを三乗した別のリストを作成しています。

In [1]:
list = [1, 'a', 2, 'b', 3, 'c']
cubes = []
for num in list:
    if isinstance(num, int):
        cubes.append(num**3)
print(cubes)
[1, 8, 27]

isinstance関数で、数値を取り出した場合のみ処理を行なっていますね。isinstance関数は「Pythonの変数の型を調べる方法」で解説しています。

2.2. リスト内包表記でif文を使ったリスト作成

リスト内包表記にif文を含めるには、次のように後ろに書きます。

In [ ]:
 for 変数 in イテラブル if 条件式

それでは先ほどと同じリストを、リスト内包表記で作成してみましょう。

In [2]:
list = [1, 'a', 2, 'b', 3, 'c']
cubes = [num**3 for num in list if isinstance(num, int)]
print(cubes)
[1, 8, 27]

こちらの方が見た目もはるかにスッキリとした簡潔なコードになっていますね。

if文の条件分岐を変えると、数値ではなく文字列を取り出すこともできます。

In [3]:
list = [1, 'a', 2, 'b', 3, 'c']
char_sqs = [char*2 for char in list if isinstance(char, str)]
print(char_sqs)
['aa', 'bb', 'cc']

2.3. 速度比較

それではif文が入った場合の、通常の作成方法とリスト内包表記の速度比較をしてみましょう。10万回のトライアルを行って比較しています。

まず通常の作成方法では、平均速度1.45マイクロ秒(標準偏差37.1ナノ秒)でした。

In [4]:
%%timeit -n 100000
list = [1, 'a', 2, 'b', 3, 'c']
cubes = []
for num in list:
    if isinstance(num, int):
        cubes.append(num**3)
1.45 µs ± 37.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

一方で、リスト内包表記の場合は1.47マイクロ秒(標準偏差41.1ナノ秒)でした。

In [5]:
%%timeit -n 100000
list = [1, 'a', 2, 'b', 3, 'c']
cubes = [num**3 for num in list if isinstance(num, int)]
1.47 µs ± 41.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

速度の面では違いはないという結果になりました。しかし、コードの見た目や行数はリスト内包表記の方がはるかに簡潔なので、やはりリスト内包表記の方が良いですね。

3. リスト内包表記にif ~ elseを含める(三項演算)

次にif文とelseを含む三項演算のリスト内包表記を見てみましょう。同じく、最初に通常のfor文を使ったリスト作成を行い、それをリスト内包表記で書き換えます。そして、最後に速度比較を行います。

3.1. for文とif ~ elseを使ったリスト作成

以下では通常のforループで、取り出した値が数値の場合は’num’を、そうでない場合は’char’をリストに追加しています。

In [1]:
list = [10, 'あ', 3, '漢字', 60, 'A']
num_chars = []
for i in list:
    if isinstance(i, int):
        num_chars.append('num')
    else:
        num_chars.append('char')
print(num_chars)
['num', 'char', 'num', 'char', 'num', 'char']

3.2. リスト内包表記でif ~ elseを使ったリスト作成

このif~elseの三項演算は、リスト内包表記では次のように書きます。

In [ ]:
条件に該当する場合の処理 if 条件式 else 該当しない場合の処理 for 変数 in イテラブル

このようにif文だけの場合は後ろに書きますが、ifとelseの三項演算は前に書きます。それではリスト内包表記で先ほどと同じリストを作ってみましょう。

In [2]:
list = [10, 'あ', 3, '漢字', 60, 'A']
num_chars = ['num' if isinstance(i, int) else 'char' for i in list]
print(num_chars)
['num', 'char', 'num', 'char', 'num', 'char']

一行で書くことができるのではるかに簡潔ですね。

3.3. 速度比較

それではifとelseが入った場合の速度比較を行いましょう。10万回のトライアルの平均速度を求めています。

まず、通常の作成方法では平均999ナノ秒(標準偏差61.1ナノ秒)でした。

In [3]:
%%timeit -n 100000
list = [10, 'あ', 3, '漢字', 60, 'A']
num_chars = []
for i in list:
    if isinstance(i, int):
        num_chars.append('num')
    else:
        num_chars.append('char')
999 ns ± 61.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

一方で、リスト内包表記では平均923ナノ秒(標準偏差66.1ナノ秒)でした。

In [4]:
%%timeit -n 100000
list = [10, 'あ', 3, '漢字', 60, 'A']
num_chars = ['num' if isinstance(i, int) else 'char' for i in list]
923 ns ± 66.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

今回は、速度面でもコードの簡潔さでもリスト内包表記に軍配が上がりましたね。やはり、通常のfor文によるリスト作成よりもリスト内包表記の方が良いようです。

4. 多重リスト内包表記

続いて、イテラブルが多重リストの場合のリスト内包表記の書き方を見てみましょう。まずは通常のfor文の場合です。

4.1. for文で多重リストからリストを作成

多重リストの要素を一つずつ取り出すには、次のように2回forループを回します。

In [1]:
list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
nums = []

for i in list:
    for j in i:
        nums.append(j**3)
        
print(nums)
[1, 8, 27, 64, 125, 216, 343, 512, 729]

このように、まず変数 i に外側の要素[1, 2, 3]を代入してから、次のその i から一つずつ変数 j に値を取り出します。全て取り出したら、また外側のループに戻り、今度は変数 i に[4, 5, 6]を代入します。そして、変数 j にその値を一つずつ取り出します。変数 j に一つずつ取り出した値を、「nums.append(j**3)」で、三乗した上で、空のリストに追加していっています。

この多重forループについては、「Pythonのfor文(forループ)の基本」でも解説していますね。

4.2. リスト内包表記で多重リストからリスト作成

リスト内包表記でも考え方は同じです。次のように、最初に「for 変数1 in 多重イテラブル」で変数1に多重イテラブルの外側の要素を取り出したら、次に「for 変数2 in 変数1」で、変数2に変数1の要素を一つずつ代入していきます。

In [ ]:
[ for 変数1 in 多重イテラブル
       for 変数2 in  変数1]

ここでは簡単化のために二行で書いていますが、続けて一行で「式 for 変数1 in 多重イテラブル for 変数2 in 変数1」と書く方が多いでしょう。

早速、次のコードを確認してみましょう。

In [2]:
list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
nums = [j**3 for i in list
                  for j in i]
print(nums)
[1, 8, 27, 64, 125, 216, 343, 512, 729]

式が「j**3」なので、リスト内包表記でこの「j」の値を指定します。まずは「for i in list」で、変数 i に多重リストの外側の要素を代入し、「for j in i」で i に代入された外側の要素を一つずつ j に代入しています。

もちろん、次のように一行で構いません。

In [3]:
nums = [j**3 for i in list for j in i]
print(nums)
[1, 8, 27, 64, 125, 216, 343, 512, 729]

三重リスト以上になっても考え方は変わりません。

In [4]:
list = [[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]]
nums = [k**3 for i in list for j in i for k in j]
print(nums)
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000, 1331, 1728]

また別々のリストから次のように、リスト内包表記で新しいリストを作ることもできます。

In [5]:
plot = [(x, y) for x in range(3) for y in range(2)]
print(plot)
[(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]

通常for文で繰り返しイテラブルから要素を取り出す操作を行なっている内に、自然と理解が深まっていくでしょう。

4.3. 速度比較

それでは多重forループの場合の通常のリスト作成方法とリスト内包表記の速度を比較しましょう。10万回のトライアルの平均速度です。

まず通常の書き方では平均3.06マイクロ秒(標準偏差126ナノ秒)でした。

In [6]:
%%timeit -n 100000
list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
nums = []

for i in list:
    for j in i:
        nums.append(j**3)
3.06 µs ± 126 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

一方で、リスト内包表記では平均2.79マイクロ秒(標準偏差87.1ナノ秒)でした。

In [7]:
%%timeit -n 100000
list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
nums = [j**3 for i in list for j in i]
2.79 µs ± 87.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

処理速度でも見た目の簡潔さでもリスト内包表記の方が優れていますね。

4.4. 多重リスト内包表記のif文

次に、多重のリスト内包表記のif文を見てみましょう。基本は一重リストの場合と全く同じです。

以下のコードではリストから変数 i に代入した要素を、変数 j に一つずつ取り出しますが、その際に、「i から取り出した要素が数値の場合」という条件を書いています。

In [8]:
list = [[1, 2, '三'], [4, 5, '六'], [7, 8, '九']]

nums = [j for i in list
                  for j in i if not isinstance(j, str)]

print(nums)
[1, 2, 4, 5, 7, 8]

以下のコードは、list_aから変数 x に要素を代入する時に「x が整数の場合」という条件を、list_bでも同様の条件を書いています。

In [9]:
list_a = [0, 'a', 1, 'b', 2, 'c']
list_b = [0, 'あ', 1, 'い']

nums = [(x,y) for x in list_a if isinstance(x, int)
                  for y in list_b if isinstance(y, int)]

print(nums)
[(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]

4.5. 多重リスト内包表記のifとelse(三項演算)

もちろん、多重リスト内包表記でも三項演算式を書くことができます。次の例では、取り出した値が整数の時は ‘num’ 、そうでない場合は’str’になるようにしています。

In [10]:
list = [[0, '一', 2], ['三', 4], ['五', 6, '七'], [8, '九']]

nums = ['num' if isinstance(j, int) else 'str'
                for i in list
                for j in i]
print(nums)
['num', 'str', 'num', 'str', 'num', 'str', 'num', 'str', 'num', 'str']

5. まとめ

ここまで見てきたようにリスト内包表記は、for文を普通に使ってリストを作る方法よりも、全体的に速度が早い傾向があり、コードははるかに簡潔に書くことができる「Pythonらしい」書き方です。

速度とコードの簡潔さというメリットは非常に大きいものなので、ぜひ使いこなせるようになっておきたいものと言えます。

一つずつ練習して使いこなせるようになっていきましょう。



よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

目次
閉じる