内包表記とは

リストの要素を一つずつ処理して、新しいリストを作るとき、通常はfor文とappend()を使います。

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

同じ処理をリスト内包表記で書くと、次のようになります。

squares = [number ** 2 for number in range(1, 6)]
print(squares) # [1, 4, 9, 16, 25]

基本形は[式 for 変数 in イテラブル]です。forで取り出した値を先頭の式へ渡し、計算した値を一つずつリストに追加します。inの後ろには、リストやrange()など、for文で要素を取り出せるものを指定します。これをイテラブルと呼びます。

リスト内包表記の基本

range(5)から取り出した数を加工せずにリストへ入れるには、forの前にも同じ変数numberを書きます。

numbers = [number for number in range(5)]
print(numbers) # [0, 1, 2, 3, 4]

リスト変数から一つずつ値を取り出し、加工することもできます。
例えばリストの文字列をすべて大文字に変換したい場合は下記のようになります。

names = ['alice', 'bob', 'carol']
upper_names = [name.upper() for name in names]
print(upper_names) # ['ALICE', 'BOB', 'CAROL']

ifで要素を絞り込む

通常のfor文では、append()の前に条件を指定すると絞り込めます。

even_numbers = []
for number in range(10):
if number % 2 == 0:
even_numbers.append(number)

内包表記では末尾にifを付けます。

even_numbers = [number for number in range(10) if number % 2 == 0]
print(even_numbers) # [0, 2, 4, 6, 8]

条件を組み合わせることもできます。次は、0から19までの数から、2と3の両方で割り切れる値を取り出します。

numbers = [
number
for number in range(20)
if number % 2 == 0 and number % 3 == 0
]
print(numbers) # [0, 6, 12, 18]

if ... elseで値を切り替える

要素を除外するのではなく、条件によって結果の値を変えたい場合は、先頭の式に条件式を書きます。

labels = [
'even' if number % 2 == 0 else 'odd'
for number in range(5)
]
print(labels) # ['even', 'odd', 'even', 'odd', 'even']

末尾のifと並べると、違いが見えます。

numbers = [-2, -1, 0, 1, 2]
# 負の値を除外する
filtered = [number for number in numbers if number >= 0]
print(filtered) # [0, 1, 2]
# 負の値を0に置き換える。要素数は変わらない
replaced = [number if number >= 0 else 0 for number in numbers]
print(replaced) # [0, 0, 0, 1, 2]

elseを伴う条件式はforより前、要素を絞り込むifはforより後ろに置きます。

辞書内包表記

波括弧の中にキー: 値を書くと、辞書を作れます。

squares = {number: number ** 2 for number in range(1, 6)}
print(squares) # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

既存の辞書を加工する場合は、items()でキーと値を取り出します。

user_ids = {'alice': 101, 'bob': 102}
users_by_id = {user_id: name for name, user_id in user_ids.items()}
print(users_by_id) # {101: 'alice', 102: 'bob'}

入れ替え後のキーが重複すると、後から処理した値で上書きされます。キーと値を入れ替える前に、元の値が重複していないか確認してください。

集合内包表記

波括弧の中に式だけを書くと、集合を作れます。集合では重複が取り除かれます。

words = ['Apple', 'apple', 'Banana', 'banana']
normalized_words = {word.lower() for word in words}
print(normalized_words == {'apple', 'banana'}) # True

辞書内包表記は{キー: 値 for ...}、集合内包表記は{式 for ...}です。集合の表示順は決まっていないため、上の例では順序に依存しない比較をしています。

ジェネレーター式

丸括弧を使うと、ジェネレーター式になります。リスト内包表記は結果のリストをその場で作りますが、ジェネレーター式は値を求められたときに順番に生成します。

squares = (number ** 2 for number in range(5))
print(next(squares)) # 0
print(next(squares)) # 1
print(list(squares)) # [4, 9, 16]

ジェネレーターは、値を取り出すたびに次の値へ進みます。上の例では、2回のnext()で0と1を取り出しているため、その後のlist(squares)には残りの4、9、16だけが入ります。

合計を求めるには、ジェネレーター式をsum()へ直接渡せます。途中でリストを作る必要はありません。

total = sum(number ** 2 for number in range(5))
print(total) # 30

このように関数の引数がジェネレーター式だけなら、追加の丸括弧は省略できます。

二重ループを一つの内包表記にする

複数のforを続けて書くと、通常の二重ループと同じ順番で処理します。

pairs = []
for x in range(3):
for y in range(2):
pairs.append((x, y))
print(pairs) # [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]
pairs = [(x, y) for x in range(3) for y in range(2)]
print(pairs) # [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]

二次元リストを一列に並べる場合も同じ形です。

matrix = [[1, 2], [3, 4], [5, 6]]
flattened = [value for row in matrix for value in row]
print(flattened) # [1, 2, 3, 4, 5, 6]

for row in matrixで行を取り出し、その行からfor value in rowで各値を取り出します。

ネストして二次元の形を保つ

各行をリストのまま残したいときは、先頭の式に別のリスト内包表記を置きます。

matrix = [[1, 2, 3], [4, 5, 6]]
doubled = [[value * 2 for value in row] for row in matrix]
print(doubled) # [[2, 4, 6], [8, 10, 12]]

先ほどのflattenedは一つのリストになりました。こちらは外側のforが行ごとのリストを作るため、二次元の形が残ります。長くなったら改行すると追いやすくなります。

matrix = [[1, 2, 3], [4, 5, 6]]
doubled = [
[value * 2 for value in row]
for row in matrix
]

複雑な内包表記を読み解く

二重ループと条件を組み合わせた例です。

matrix = [[1, 2, 3], [4, 5, 6]]
results = [
value * 2
for row in matrix
for value in row
if value % 2 == 0
]
print(results) # [4, 8, 12]

まずmatrixから行を取り出し、行から値を取り出します。次に偶数だけを残し、最後に2倍した値を結果へ入れます。
条件が複数になる場合はfor文で書いた方がわかりやすい場合もあります。

matrix = [[1, 2, 3], [4, 5, 6]]
results = []
for row in matrix:
for value in row:
if value % 2 == 0:
results.append(value * 2)

書き方の早見表

作るもの・処理 書き方
リスト [式 for x in iterable]
条件で絞り込む [式 for x in iterable if 条件]
値を切り替える [A if 条件 else B for x in iterable]
辞書 {キー: 値 for x in iterable}
集合 {式 for x in iterable}
ジェネレーター (式 for x in iterable)

内包表記に慣れるまでは、同じ処理をfor文でも書いて比べると、要素を取り出してから結果のリストに追加するまでの流れを理解しやすくなると思います!