Pythonリスト内包表記の使い方

Modified: | Tags: Python, リスト

Pythonでは、リスト内包表記(List comprehensions)を使うと新しいリストを生成する処理をシンプルに書ける。

for文の基本については以下の記事を参照。

リスト内包表記を活用した具体例は以下の記事を参照。

リスト内包表記の基本型

リスト内包表記は以下のように書く。

[ for 任意の変数名 in イテラブルオブジェクト]

リストやタプル、rangeなどのイテラブルオブジェクトの各要素を任意の変数名で取り出しで評価、その結果を要素とする新たなリストが返される。

等価なfor文とともに例を示す。rangeの要素(連番)を取り出して2乗する。

squares = [i**2 for i in range(5)]
print(squares)
# [0, 1, 4, 9, 16]
squares = []
for i in range(5):
    squares.append(i**2)

print(squares)
# [0, 1, 4, 9, 16]

map()でも同様の処理ができるが、リスト内包表記のほうがコードが簡潔・明解で好ましいとされている。

ifで条件分岐したリスト内包表記

ifで条件分岐することも可能。以下のように後置でifを記述する。

[ for 任意の変数名 in イテラブルオブジェクト if 条件式]

条件式Trueとなるイテラブルオブジェクトの要素のみで評価され、その結果が要素となる新たなリストが返される。Falseとなる要素は要素は無視される。

条件式の中でも任意の変数名が使える。

等価なfor文とともに例を示す。rangeの要素(連番)から奇数のみを取り出す。

odds = [i for i in range(10) if i % 2 == 1]
print(odds)
# [1, 3, 5, 7, 9]
odds = []
for i in range(10):
    if i % 2 == 1:
        odds.append(i)

print(odds)
# [1, 3, 5, 7, 9]

filter()でも同様の処理ができるが、こちらもリスト内包表記のほうがコードが簡潔・明解で好ましいとされている。

リスト内包表記と三項演算子の組み合わせ(if else的な処理)

上の例では、条件を満たす要素のみが処理され、条件を満たさない要素は新たなリストから除外される。

条件によって処理を切り替えたい、if elseのように条件を満たさない要素には別の処理を行いたい場合は三項演算子を使う。

Pythonでは三項演算子は以下のように書ける。

真のときの値 if 条件式 else 偽のときの値

これを以下のようにリスト内包表記のの部分に使う。

[真のときの値 if 条件式 else 偽のときの値 for 任意の変数名 in イテラブルオブジェクト]

等価なfor文とともに例を示す。rangeの要素(連番)を取り出し、奇数は'odd'、偶数は'even'に置き換える。

odd_even = ['odd' if i % 2 == 1 else 'even' for i in range(10)]
print(odd_even)
# ['even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd']
odd_even = []
for i in range(10):
    if i % 2 == 1:
        odd_even.append('odd')
    else:
        odd_even.append('even')

print(odd_even)
# ['even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd']

真のときの値, 偽のときの値任意の変数名を使った式を記述することもできる。

条件を満たす場合は何らかの処理を行い、満たさない場合は元のイテラブルオブジェクトの値そのままというようなことも可能。

odd10 = [i * 10 if i % 2 == 1 else i for i in range(10)]
print(odd10)
# [0, 10, 2, 30, 4, 50, 6, 70, 8, 90]

リスト内包表記とzip(), enumerate()の組み合わせ

for文でよく使われる便利な関数に、複数のイテラブルをまとめるzip()やインデックスとともに値を返すenumerate()がある。

zip()enumerate()をリスト内包表記で使うことももちろん可能。特殊な文法というわけではなく、for文との対応を考えれば難しくない。

zip()の例。

l_str1 = ['a', 'b', 'c']
l_str2 = ['x', 'y', 'z']

l_zip = [(s1, s2) for s1, s2 in zip(l_str1, l_str2)]
print(l_zip)
# [('a', 'x'), ('b', 'y'), ('c', 'z')]
l_zip = []
for s1, s2 in zip(l_str1, l_str2):
    l_zip.append((s1, s2))

print(l_zip)
# [('a', 'x'), ('b', 'y'), ('c', 'z')]

enumerate()の例。

l_enu = [(i, s) for i, s in enumerate(l_str1)]
print(l_enu)
# [(0, 'a'), (1, 'b'), (2, 'c')]
l_enu = []
for i, s in enumerate(l_str1):
    l_enu.append((i, s))

print(l_enu)
# [(0, 'a'), (1, 'b'), (2, 'c')]

ifを使う場合もこれまで通りの考え方。

l_zip_if = [(s1, s2) for s1, s2 in zip(l_str1, l_str2) if s1 != 'b']
print(l_zip_if)
# [('a', 'x'), ('c', 'z')]

それぞれの要素を使って新たな要素を算出することもできる。

l_int1 = [1, 2, 3]
l_int2 = [10, 20, 30]

l_sub = [i2 - i1 for i1, i2 in zip(l_int1, l_int2)]
print(l_sub)
# [9, 18, 27]

ネストしたリスト内包表記(多重ループ)

forループをネストするように、リスト内包表記もネストできる。

[ for 変数名1 in イテラブルオブジェクト1
    for 変数名2 in イテラブルオブジェクト2
        for 変数名3 in イテラブルオブジェクト3 ... ]

便宜上、改行とインデントを加えているが、文法としては必須ではない。一行で続けて書いても構わない。

等価なfor文とともに例を示す。

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

flat = [x for row in matrix for x in row]
print(flat)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
flat = []
for row in matrix:
    for x in row:
        flat.append(x)

print(flat)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

複数の変数を使うことも可能。

cells = [(row, col) for row in range(3) for col in range(2)]
print(cells)
# [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]

条件分岐もできる。

cells = [(row, col) for row in range(3)
         for col in range(2) if col == row]
print(cells)
# [(0, 0), (1, 1)]

それぞれのイテラブルオブジェクトに対して条件分岐することもできる。

cells = [(row, col) for row in range(3) if row % 2 == 0
         for col in range(2) if col % 2 == 0]
print(cells)
# [(0, 0), (2, 0)]

集合内包表記(Set comprehensions)

リスト内包表記の角括弧[]を波括弧{}に変更すると、集合(set型オブジェクト)が生成される。

{ for 任意の変数名 in イテラブルオブジェクト}
s = {i**2 for i in range(5)}

print(s)
# {0, 1, 4, 9, 16}
source: set.py

集合についての詳細は以下の記事を参照。

辞書内包表記(Dict comprehensions)

辞書(dict型オブジェクト)も内包表記で生成できる。

{}で囲み、の部分でキーと値の2つをキー: 値のように指定する。

{キー:  for 任意の変数名 in イテラブルオブジェクト}

キーと値には任意の式を指定可能。

l = ['Alice', 'Bob', 'Charlie']

d = {s: len(s) for s in l}
print(d)
# {'Alice': 5, 'Bob': 3, 'Charlie': 7}

キーと値それぞれのリストから新たな辞書を作成する場合はzip()関数を使う。

keys = ['k1', 'k2', 'k3']
values = [1, 2, 3]

d = {k: v for k, v in zip(keys, values)}
print(d)
# {'k1': 1, 'k2': 2, 'k3': 3}

辞書を生成するそのほかの方法については以下の記事を参照。

ジェネレータ式(Generator expressions)

リスト内包表記の角括弧[]を丸括弧()にした場合はタプルではなくジェネレータを返す。これをジェネレータ式(generator expressions)という。

リスト内包表記の例。

l = [i**2 for i in range(5)]

print(l)
# [0, 1, 4, 9, 16]

print(type(l))
# <class 'list'>

ジェネレータ式の例。ジェネレータはそのままprint()しても中身は出力されないがfor文でまわすと中身が取得できる。

g = (i**2 for i in range(5))

print(g)
# <generator object <genexpr> at 0x10af944f8>

print(type(g))
# <class 'generator'>

for i in g:
    print(i)
# 0
# 1
# 4
# 9
# 16

ジェネレータ式でもリスト内包表記と同様にifによる条件分岐やネストが可能。

g_cells = ((row, col) for row in range(0, 3)
           for col in range(0, 2) if col == row)

print(type(g_cells))
# <class 'generator'>

for i in g_cells:
    print(i)
# (0, 0)
# (1, 1)

例えば要素数が多いリストをリスト内包表記で生成してfor文でまわすような場合、リスト内包表記を使うと最初に全要素を含むリストを生成するが、ジェネレータ式を使うとループが繰り返されるごとに要素が一つずつ生成されるので、メモリの使用量を抑えることができる。

ジェネレータ式を関数の唯一の引数とする場合は丸括弧()を省略できる。

print(sum([i**2 for i in range(5)]))
# 30

print(sum((i**2 for i in range(5))))
# 30

print(sum(i**2 for i in range(5)))
# 30

処理速度に関しては、すべての要素を処理する場合はリスト内包表記のほうがジェネレーター式よりも速いことが多いようである。

ただし、例えばall()any()で判定する場合はFalseまたはTrueが存在する時点で結果が確定するため、リスト内包表記よりもジェネレーター式を使うほうが処理速度が速くなる場合がある。

タプル内包表記はないが、ジェネレータ式をtuple()の引数とすると内包表記の書き方でタプルを生成できる。

t = tuple(i**2 for i in range(5))

print(t)
# (0, 1, 4, 9, 16)

print(type(t))
# <class 'tuple'>

関連カテゴリー

関連記事