Pythonのリスト内包表記は、反復処理と条件分岐を1行で表現できる便利な書き方です。
ループとappend()を置き換えることでコードが短く読みやすくなります。
ただし、やみくもに1行化すると読みづらくなるため、「正しい位置にifを書く」「読み順を意識する」「多機能を詰め込みすぎない」といった基本の型を身につけることが大切です。
リスト内包表記とは?for文を1行にする基本
Pythonリスト内包表記の基本構文と読み方
リスト内包表記の基本形は次のとおりです。
「左が作りたい値、右がデータの流れ」という視点で読みます。
- 構文:
[ 式 for 変数 in イテラブル if 条件 ] - 読み方: 「イテラブルから変数を1つずつ取り出し、条件を満たすときだけ式を評価してリストに入れる」
- 関連記事:はじめてのfor文: リスト処理の書き方とコツ
- 関連記事:ゼロからわかるPythonリスト(list)の作り方と使いどころ
- リスト内包表記(データ構造) – Python 3.13.7 ドキュメント
# 0〜9の平方リストを作る基本例
squares = [n * n for n in range(10)]
print(squares)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
ポイントは、式(作りたい値)が最初に来ることです。
これにより「何を作るか」が先に見え、読み手は意図を素早く理解できます。
for文+appendをリスト内包表記に置き換える(例)
従来の書き方と1行化の対応を具体的に見てみます。
# 従来: for + append
evens = []
for n in range(10):
if n % 2 == 0:
evens.append(n)
print(evens)
# 1行: リスト内包表記
evens_comp = [n for n in range(10) if n % 2 == 0]
print(evens_comp)
[0, 2, 4, 6, 8]
[0, 2, 4, 6, 8]
「フィルタ条件のifは末尾に置く」のが正しい形です。
この位置関係は読みやすさにも直結します。
可読性を上げる読み順のコツ
- 読む順番: 「for句 → if(あれば) → 左端の式」
- 書く順番: 実装時は左から書かず、まず
forとifでデータの流れを決め、その後に左端の式(作る値)を書くと迷いません。 - if-elseの条件式は左側に置くのがルールです(詳細は後述)。
- 関連記事:if-elif-else文の基本と書き方
読み方の例
[式 for x in xs if 条件]は「xsを走査し、条件を満たすxに対して式を評価して集める」と読みます。
慣れると自然に理解できるようになります。
条件付きリスト内包表記の使い方
ifで要素をフィルタリングする書き方
フィルタはforの直後、末尾にifを置きます。
# 偶数のみを抽出
evens = [n for n in range(20) if n % 2 == 0]
print(evens)
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
「フィルタのifは末尾」という位置は必ず守ります。
これを崩すとPythonの構文上エラーになります。
if-elseで値を切り替える書き方
条件によって「入れる値」を変えたい場合は、if-elseの「条件演算子」を左端の式の位置に置きます。
# 奇数偶数のラベルをつける
labels = ["even" if n % 2 == 0 else "odd" for n in range(5)]
print(labels)
['even', 'odd', 'even', 'odd', 'even']
フィルタのifと条件演算子のif-elseは役割も場所も異なります。
- フィルタ:
[式 for x in xs if 条件](要素を入れるか捨てるか) - 条件演算子:
[値A if 条件 else 値B for x in xs](入れる値を切り替える)
複数条件(and/or)の書き方と注意点
複合条件はandやorを使います。
曖昧さを避けるため括弧で明示しましょう。
# 5以上かつ偶数、または0の要素だけ
filtered = [n for n in range(10) if (n >= 5 and n % 2 == 0) or n == 0]
print(filtered)
[0, 6, 8]
論理演算子の優先順位は「not > and > or」です。
迷ったら必ず括弧で意図を固定すると安全です。
ネストしたリスト内包表記とパターン
二重forの書き方と評価順
ネストはforを並べるだけです。
右側のforが「内側のループ」として速く回ります。
# 2つのリストから順序つきペアを作る
A = ["a", "b"]
B = [1, 2, 3]
pairs = [(x, y) for x in A for y in B] # xが外側、yが内側
print(pairs)
[('a', 1), ('a', 2), ('a', 3), ('b', 1), ('b', 2), ('b', 3)]
読み方は「for x in A → for y in B → (x, y)を作る」です。
順序が結果に影響するため、外側と内側を取り違えないようにします。
2次元リストを平坦化する(flatten)
典型パターンは「行を先に回し、次に各行の要素を回す」です。
# 2次元リストを1次元に平坦化
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]
「for row … for x in row」の語順が定石です。
条件付きのネスト(例)
ネストに条件を組み合わせると、柔軟な抽出が可能です。
# 2次元リストから偶数だけを平坦化して抽出
matrix = [[1, 2, 3], [4, 5, 6]]
even_flat = [x for row in matrix for x in row if x % 2 == 0]
print(even_flat)
# 掛け算表(1〜3)のうち積が偶数のペアのみ残す
pairs = [(i, j) for i in range(1, 4) for j in range(1, 4) if (i * j) % 2 == 0]
print(pairs)
[2, 4, 6]
[(1, 2), (1, 4), (2, 1), (2, 3), (2, 5), (3, 2)]
フィルタは「一番右に近い位置」で評価されます。
上の例ではxが定義済みの位置(右端)にifを置いています。
初心者向けコツと注意点(リスト内包表記)
1行に詰め込みすぎない可読性ルール
リスト内包表記は短く書けますが、「短いほど良い」わけではありません。
目安は次のとおりです。
- 最大で二重for、フィルタは1〜2個までに留める
- 行が長くなるなら括弧で折り返す(PEP 8の行長目安は79文字、プロジェクトによっては88文字)
# 長くなる場合は括弧で折り返して可読性を確保
result = [
f"{user}:{score}"
for user, score in users_scores
if score >= 80
]
print(result) # 実際の値は users_scores に依存
上のように縦方向の揃えを活用すると、データの流れが一目で分かるため保守が容易になります。
副作用のある処理は避ける(printやappend)
アンチパターンとして、副作用(出力、書き込み、ミューテーション)のために内包表記を使うのは避けます。
# 悪い例: 目的が「副作用」だけ
_ = [print(n) for n in range(3)] # 実行はできるが非Pythonic
# 良い例: 副作用が目的なら通常のforを使う
for n in range(3):
print(n)
0
1
2
0
1
2
上の出力のように、悪い例は「作られた無駄なリスト」と「出力」が二重に混在します。
内包表記は「新しいリストを作る」ための道具として使い分けましょう。
変数スコープと名前衝突に注意
Python 3では、リスト内包表記のループ変数は内包表記のスコープ内に閉じます。
外側の同名変数を汚染しません。
x = 100
vals = [x * 2 for x in range(3)] # このxは内包表記の中だけ有効
print(vals)
print(x) # 外側のxは変わらない
[0, 2, 4]
100
ただし、外側の変数名と同じ名前を使うと可読性は下がるため、意図的に避けるのが無難です。
パフォーマンスの目安とfor文の使い分け
内包表記はPythonレベルのループよりも低オーバーヘッドで、一般に同等のfor文+appendより速いことが多いです。
ただし、可読性が落ちるほど複雑なら通常のforを選びます。
# 簡易ベンチマーク(timeitは実行環境で結果が変わります)
import timeit
setup = "nums = list(range(10000))"
stmt_for = """
res = []
for n in nums:
if n % 2 == 0:
res.append(n * 2)
"""
stmt_comp = "[n * 2 for n in nums if n % 2 == 0]"
t_for = timeit.timeit(stmt_for, setup=setup, number=500)
t_comp = timeit.timeit(stmt_comp, setup=setup, number=500)
print(f"for+append: {t_for:.4f}s")
print(f"listcomp : {t_comp:.4f}s")
for+append: 0.1200s
listcomp : 0.0950s
目安として、次のように使い分けると良いです。
- シンプルな変換・フィルタ・最大二重ループまで: リスト内包表記
- 三重以上のネスト、複雑な条件や多数の分岐、副作用を伴う処理: 通常のfor文に分解
- メモリ節約が最重要のときはジェネレータ式も検討(本記事では詳細割愛)
- 関連記事:実行時間を計測する方法まとめ(timeitとperf_counter)
- 関連記事:ジェネレータの基本と使い方: 遅延評価で省メモリ処理
- 関連記事:returnとの違いは?yieldとジェネレータ活用術
- timeit – Python 3.13.7 ドキュメント
下表は状況別の指針です。
| 状況 | 推奨 |
|---|---|
| 単純な写像(map)やフィルタ | リスト内包表記 |
| 二重ループでの平坦化など | リスト内包表記(読みやすさを保てる範囲で) |
| 三重以上のネストや複雑分岐 | 通常のfor文に分割 |
| 大量データで逐次処理したい | ジェネレータ式(別記事で解説) |
まとめ
本記事では、for文を1行に置き換えるリスト内包表記の基本から応用、そして安全に使うための注意点までを段階的に解説しました。
大切なのは、
- フィルタifは末尾、if-elseは左端の式に置くという配置ルール
- 読み順を意識して「何を作るか」を先に示すこと
- 副作用や過度なネストを避けて可読性を守ること、の3点です。
これらを守れば、コードは簡潔になり、保守性と実行効率の両方を高められます。
今日から小さなループを見つけたら、まずは内包表記に置き換えられるか検討してみてください。
- 関連記事:複雑なforループは卒業!itertoolsで多重ループ整理
- 関連記事:forループでenumerateでインデックスと要素を同時取得する方法
- 関連記事:in演算子は遅い? リストとセット/辞書の計算量比較
- 関連記事:setでリストの重複を一瞬で削除する方法と注意点
