Pythonのリスト内包表記は、for文で行っていた反復処理と新しいリスト作成を1行で表現できる強力な書き方です。
慣れると記述量が減るだけでなく、意図が明確になり読みやすいコードになります。
本記事では、基本から条件付き、ネストした応用、避けるべき書き方まで、初心者の方が段階的に身につけられるように詳しく解説します。
Pythonリスト内包表記の基本
構文の書き方と読み方
リスト内包表記の基本形は [(式) for (変数) in (イテラブル) if (条件)]
です。
最後の if (条件)
は省略可能で、ある場合はフィルタとして働きます。
また、分岐して値を切り替えたい場合は、先頭の式に A if 条件 else B
の形で書きます。
リスト内包表記の全体像を読み解くときは「左から右」ではなく「for節から式へ」の順で読むと理解しやすいです。
つまり「イテラブルから要素を取り出し、必要なら条件で絞り、式で変換して新しいリストに詰める」と捉えます。
次の表は、よく使う2パターンの形と意味の対応です。
形 | 意味 |
---|---|
[式 for x in xs] | xsの各要素xを式で変換して並べる |
[式 for x in xs if 条件] | 条件を満たすxだけを式で変換して並べる |
[A if 条件 else B for x in xs] | 各xごとに条件でAかBを選んで並べる |
最小例(2乗のリスト)
# 0〜4の2乗をリスト化する
squares = [n * 2 for n in range(5)]
print(squares)
[0, 2, 4, 6, 8]
for文をリスト内包表記で置き換える
同じ処理をfor文とリスト内包表記で比較します。
どちらも正しいですが、単に「集める」だけなら内包表記が簡潔です。
# for文で新しいリストを作る
result_loop = []
for n in range(1, 6):
result_loop.append(n ** 2)
print("for文:", result_loop)
# リスト内包表記で同じ処理
result_comp = [n ** 2 for n in range(1, 6)]
print("内包表記:", result_comp)
for文: [1, 4, 9, 16, 25]
内包表記: [1, 4, 9, 16, 25]
リスト内包表記のメリット
リスト内包表記には次の利点があります。
可読性の観点で「何を作るか」を先頭に書けるのが特に大きいです。
- 簡潔で意図が明確になる(作りたいリストを1文で表せる)
- 中間的なappend呼び出しが不要で見通しが良い
- 同等のfor文より高速になることが多い(Pythonの実装最適化の恩恵)
ただし、条件やネストが増え過ぎると逆に読みにくくなるため、後述の注意点も合わせて意識します。
条件付きの使い方
ifで要素をフィルタする
末尾に if 条件
を書くと、条件を満たす要素だけを残せます。
値の変換が不要なら式にそのまま変数を置きます。
# 偶数だけを抽出する
nums = list(range(10))
evens = [n for n in nums if n % 2 == 0]
print(evens)
[0, 2, 4, 6, 8]
参考として同等のfor文も見ておきます。
nums = list(range(10))
evens_loop = []
for n in nums:
if n % 2 == 0:
evens_loop.append(n)
print(evens_loop)
[0, 2, 4, 6, 8]
if elseで値を切り替える
末尾のifはフィルタですが、値を分岐して入れたい場合は式側に A if 条件 else B
と書きます。
位置が異なる点に注意します。
# 偶数なら "even"、奇数なら "odd" の文字列に変換
labels = ["even" if n % 2 == 0 else "odd" for n in range(5)]
print(labels)
['even', 'odd', 'even', 'odd', 'even']
式側にif/elseを置く場合の語順は「値 if 条件 else 値」です。
C系言語の三項演算子と順序が異なるので慣れるまで注意してください。
空要素やNoneを除外する
空文字やNoneを除外する方法は2通りあります。
手軽なのは真偽値(Truthiness)に従ってフィルタする方法ですが、0も偽として扱われるため、除外対象を厳密に指定したい場合は条件を明示します。
items = ["Alice", "", None, "Bob", 0, "Charlie"]
# 1. 真偽値でフィルタ(空文字、None、0など偽と判定される値が除外される)
filtered_truthy = [x for x in items if x]
print(filtered_truthy)
# 2. 除外対象を明示してフィルタ(0は残し、空文字とNoneだけ除外)
filtered_explicit = [x for x in items if x is not None and x != ""]
print(filtered_explicit)
['Alice', 'Bob', 'Charlie']
['Alice', 'Bob', 0, 'Charlie']
ネストと応用パターン
二重ループのネストを1行にする
内包表記は複数のfor節を続けて書けます。
二重ループを1行にする典型例は直積(全ての組み合わせ)です。
# 2つのリストの全組み合わせ(直積)を作る
colors = ["red", "green"]
sizes = ["S", "M", "L"]
pairs = [(c, s) for c in colors for s in sizes]
print(pairs)
[('red', 'S'), ('red', 'M'), ('red', 'L'), ('green', 'S'), ('green', 'M'), ('green', 'L')]
読み方は通常の二重ループと同じ順序です。
「colorsを回して、その内側でsizesを回す」という順に並べます。
二次元リストをフラット化する
行列のような二次元リストを1次元に平坦化する場合は、外側の行を先に、内側の要素を後にfor節を並べます。
# 2次元リストをフラット化する
matrix = [
[1, 2, 3],
[4, 5, 6],
]
flat = [x for row in matrix for x in row]
print(flat)
[1, 2, 3, 4, 5, 6]
enumerateやrangeの活用
添字と値を同時に扱いたい場合は enumerate
と組み合わせると明快です。
新しい構造を作る場合に特に相性が良いです。
# 文字列の各文字に添字を付けてタプルにする
s = "python"
indexed = [(i, ch) for i, ch in enumerate(s, start=1)]
print(indexed)
[(1, 'p'), (2, 'y'), (3, 't'), (4, 'h'), (5, 'o'), (6, 'n')]
range
との組み合わせもよく使います。
例えば九九の一部を作る例を示します。
# 1〜3の掛け算表(タプルで格納)
table = [(i, j, i * j) for i in range(1, 4) for j in range(1, 4)]
print(table)
[(1, 1, 1), (1, 2, 2), (1, 3, 3), (2, 1, 2), (2, 2, 4), (2, 3, 6), (3, 1, 3), (3, 2, 6), (3, 3, 9)]
リスト内包表記の注意点とコツ
複雑化を避けて可読性を保つ
内包表記は「1つの変換」と「簡単なフィルタ」までが読みやすさの限界です。
for節が3つ以上、条件が複数、またはネストしたifが出てくる場合は、通常のfor文や補助関数に分けた方が安全です。
悪い例と改善例を見比べます。
# 悪い例: 条件とネストが多く読みにくい
data = [("alice", 20), ("bob", 17), ("carol", 22)]
# 大文字化した名前を大人だけ抽出し、IDを連番で振る…を1行でやろうとしてしまう
ugly = [(i, name.upper()) for i, (name, age) in enumerate(data) if age >= 18] # すでに読みづらい
print(ugly)
[(0, 'ALICE'), (2, 'CAROL')]
# 改善例: 役割ごとに分ける
def is_adult(rec):
_, age = rec
return age >= 18
def to_upper_name(rec):
name, _ = rec
return name.upper()
data = [("alice", 20), ("bob", 17), ("carol", 22)]
adults = [rec for rec in data if is_adult(rec)]
upper_names = [to_upper_name(rec) for rec in adults]
indexed = list(enumerate(upper_names))
print(indexed)
[(0, 'ALICE'), (1, 'CAROL')]
ケースによっては内包表記を1度だけ使い、他は通常のfor文で段階的に処理する方が読みやすく、意図が明確です。
副作用のある処理は書かない
内包表記は「リストを作るための式」です。
ログ出力、ファイル操作、リストへの追加など副作用を目的に使うのは避けます。
副作用が欲しい場合は素直にfor文を使います。
# 悪い例: 出力のためだけに内包表記を使う(無駄にリストが作られる)
_ = [print(n) for n in range(3)]
0
1
2
# 良い例: 副作用はfor文で
for n in range(3):
print(n)
0
1
2
内包表記は返り値のリストが必要なときだけ使うのが原則です。
大きなリストのメモリ使用に注意
内包表記は完成したリストを一度にメモリに載せます。
要素数が非常に多い場合は一時メモリが膨らみます。
全件を保持する必要がないときは、イテレーションで逐次処理する設計に切り替えることを検討してください。
省メモリに処理する方法(ジェネレータ式など)は別記事で詳しく扱います。
補足として、Python 3では内包表記のループ変数(例えば n
)は内包表記のスコープ内に閉じ込められ、外側に漏れません。
ループ変数の衝突を心配する必要は基本的にありません。
まとめ
リスト内包表記は「イテラブルから要素を取り出し、必要なら条件で絞り、式で変換して新しいリストにまとめる」という処理を1行で表現できるPythonらしい構文です。
基本形 [式 for x in xs]
に、フィルタ if 条件
と式側の A if 条件 else B
を適切に組み合わせれば、日常的なデータ整形の多くを簡潔に書けます。
二重ループのネストやフラット化、enumerate
との併用も頻出パターンです。
一方で、条件やネストが増え過ぎると可読性を損ない、副作用目的での利用はアンチパターンです。
大きなデータではメモリ使用にも注意し、必要に応じて設計を見直します。
内包表記を「リストを作るための表現」として適材適所で使い分けることで、短く読みやすく、意図が伝わるPythonコードを書けるようになります。