Pythonを触り始めると、ほぼ必ず目にする構文のひとつが リスト内包表記(list comprehension)です。
一見すると難しそうに見えますが、実はコードを「短く」「読みやすく」書けるとても便利な機能です。
この記事では、リスト内包表記の基本から、条件分岐・zip/enumerateとの組み合わせ・ネスト構造・集合/辞書内包表記・ジェネレータ式まで、段階的に理解できるように丁寧に解説します。
「for 文で書けるけど、もっと Python らしく書きたい」
そんな方にぜひ読んでほしい内容です。
目次
■ リスト内包表記とは?
リスト内包表記とは、
「イテラブル(for で回せる値)に対して処理を行い、新しいリストを一行で作る書き方」
のことです。
基本の形は次のとおり。
[式 for 変数 in イテラブル]
例えば、0〜4 の数字を2乗したリストを作りたい場合:
squares = [i**2 for i in range(5)]
# → [0, 1, 4, 9, 16]
同じ処理を普通の for 文で書くとこうなります。
squares = []
for i in range(5):
squares.append(i**2)
比べると、リスト内包表記の方が圧倒的に簡潔ですよね。
Python では「短くて読みやすいコード」が推奨されるため、よく使われる書き方です。
■ 条件をつけて抽出する(if の後置)
リスト内包表記では、特定の条件を満たす要素だけ取り出すこともできます。
基本形は次の通り。
[式 for 変数 in イテラブル if 条件式]
例:0〜9 の数字から奇数だけを集める
odds = [i for i in range(10) if i % 2 == 1]
# → [1, 3, 5, 7, 9]
for 文で書くとこうなります。
odds = []
for i in range(10):
if i % 2 == 1:
odds.append(i)
後置 if を使うことで、「条件に合うものだけを残す」処理が一行で書けます。
■ if…else を使った分岐処理(条件で値を変える)
さきほどの後置 if は「フィルタリング」でしたが、条件によって別の値を入れたいという場合もあります。
そのときは三項演算子(if else の一行版)を使います。
書き方はこちら。
[真のときの値 if 条件式 else 偽のときの値 for 変数 in イテラブル]
例:偶数は ‘even’、奇数は ‘odd’ に変換する
labels = ['odd' if i % 2 else 'even' for i in range(10)]
こういった「マッピング変換」では特に威力を発揮します。
■ zip() や enumerate() との組み合わせ
for 文ではよく zip() や enumerate() が使われますが、リスト内包表記でもそのまま活用できます。
● zip の例
names1 = ['a', 'b', 'c']
names2 = ['x', 'y', 'z']
pairs = [(n1, n2) for n1, n2 in zip(names1, names2)]
# → [('a', 'x'), ('b', 'y'), ('c', 'z')]
● enumerate の例
lst = ['apple', 'banana', 'cherry']
indexed = [(i, s) for i, s in enumerate(lst)]
# → [(0, 'apple'), (1, 'banana'), (2, 'cherry')]
複数の値を扱う処理が非常にスッキリ書けます。
■ ネストした内包表記(多重ループ)
for 文にネスト(入れ子)があるなら、リスト内包表記でも同じようにネストできます。
例:二次元リストを1次元へフラット化する
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [x for row in matrix for x in row]
# → [1,2,3,4,5,6,7,8,9]
これは最初につまずきやすい部分ですが、「for の順番はそのまま右に並ぶ」と覚えると理解しやすいです。
また複数のループで条件をつけることも可能です。
cells = [(r, c) for r in range(3) if r % 2 == 0
for c in range(2) if c % 2 == 0]
# → [(0,0), (2,0)]
■ 集合内包表記(set comprehensions)
リスト内包表記とほぼ同じ構文で、角括弧を波括弧 {} に変えると set(集合) を作れます。
s = {i**2 for i in range(5)}
# → {0,1,4,9,16}
集合は重複を持てないため、
自動的に重複要素がなくなります。
■ 辞書内包表記(dict comprehensions)
辞書(dict)も内包表記で簡単に作れます。
{キー: 値 for 変数 in イテラブル}
例:文字列と文字数の辞書
names = ['Alice', 'Bob', 'Charlie']
d = {s: len(s) for s in names}
zip と併用すれば、キーと値のリストから辞書が作れます。
keys = ['k1','k2','k3']
vals = [10,20,30]
d = {k:v for k, v in zip(keys, vals)}
■ ジェネレータ式(generator expressions)
リスト内包表記の [] を () に変えると、リストではなく ジェネレータ を返します。
g = (i**2 for i in range(5))
ジェネレータは「必要なときに1つずつ値を生成」するため、大量データ処理ではメモリ効率が抜群です。
例:sum の引数として使う場合、() は省略可能
sum(i**2 for i in range(5))
- 全要素を使う処理 → 内包表記(リスト)が速いことが多い
- 部分的にしか使わない処理 → ジェネレータが有利
という特徴があります。
■ まとめ:リスト内包表記は Python の表現力を広げる強力な武器
リスト内包表記は Python らしさの象徴でもあり、
「短く」「読みやすく」「効率的」なコードを書くための非常に重要なテクニックです。
この記事で紹介した内容を整理すると:
- 基本形を覚えれば何にでも応用できる
- 条件分岐(if)、三項演算子(if…else)も使える
- zip や enumerate と相性が良い
- ネスト構造で多重ループも表現できる
- 集合や辞書も内包表記で作れる
- 丸括弧ならジェネレータになる
最初はとっつきにくく感じても、使い慣れるほど「これが一番読みやすい」と実感できます。
追補:リスト内包表記の“実務で効く”レシピ集
1) 取り出した値を使わない場合(プレースホルダとしての _)
乱数で 1〜5 を 5 個つくる例。ループ変数を使わないときは _ を慣習的に使う と読みやすくなります。
from random import randint
random_nums = [randint(1, 5) for _ in range(5)]
print(random_nums) # 例: [1, 5, 2, 1, 5]
分解ポイント
- for _ in range(5): 5回まわすが、取り出す値は使わない意図を明示
- randint(1, 5): 各回で 1〜5 の乱数を生成
※ テストで再現性が必要なら random.seed(0) などを先に呼ぶ
2) 文字とコードポイントを往復する(chr / ord)
アルファベット a〜z を作る → それを ASCII コードに変換 する流れ。
# 'a'〜'z' のリスト
chars = [chr(n + ord('a')) for n in range(26)]
print(chars)
# ['a', 'b', 'c', ..., 'x', 'y', 'z']
# 文字列へ連結
s = ''.join(chars)
print(s) # abcdefghijklmnopqrstuvwxyz
# 各文字の ASCII 値に変換
ords = [ord(c) for c in s] # もちろん [ord(c) for c in chars] でもOK
print(ords)
# [97, 98, 99, 100, 101, ..., 120, 121, 122]
分解ポイント
- ord(‘a’) は 97、chr(97) は ‘a’
- n + ord(‘a’) で 97〜122 を作り、chr(…) で文字へ
- ”.join(chars) は 文字列 を得たいときの定番
※ この例は ASCII の小文字に限定。日本語など多言語は Unicode を意識(ord/chrはUnicodeに対応)
3) enumerate で “位置+中身” をまとめて扱う
インデックスと値 を同時にタプルで持つ。UIリストの並び番号付与などに便利。
words = ['foo', 'bar', 'baz']
indexed_words = [(idx, word) for idx, word in enumerate(words)]
print(indexed_words)
# [(0, 'foo'), (1, 'bar'), (2, 'baz')]
分解ポイント
- enumerate(words) は (0,’foo’), (1,’bar’), … を順に返す
- [(idx, word) for …] の形で “構造の保存” が分かりやすい
小ワザ:1始まりにしたいなら enumerate(words, start=1)
4) zip で “要素ごとの演算” を一行に
2 つの等長リストを 要素ごと に加算していく。データ列の合成に最適。
nums1 = [1, 2, 3, 4, 5]
nums2 = [5, 4, 3, 2, 1]
sums = [a + b for a, b in zip(nums1, nums2)]
print(sums) # [6, 6, 6, 6, 6]
分解ポイント
- zip(nums1, nums2) は (1,5), (2,4), … のペアを作る
- a + b をそのまま内包表記で評価
注意:長さが違うと短い方に揃って切り捨てられる(itertools.zip_longestで補完も可)
5) 多重 for で直積(ペアの全組み合わせ)
2つの範囲の全組み合わせ を作る。座標やテストケース生成に役立つ。
nums = [[i, j] for i in range(3) for j in range(2)]
print(nums)
# [[0, 0], [0, 1], [1, 0], [1, 1], [2, 0], [2, 1]]
分解ポイント(順番に注意)
- for i in range(3) が 外側ループ
- 続く for j in range(2) が 内側ループ
- 内包表記は 左から右 にループがネストしていく
等価な for 文の順番と完全一致させると迷いにくい
6) ネスト内包で 2 次元表を作る(九九 1〜5 の段)
行(段)×列(掛ける数)を 二重の内包表記 で生成します。
multbl = [[x * y for y in range(1, 10)] # 1〜9 列
for x in range(1, 6)] # 1〜5 行(段)
for row in multbl:
print(row)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# [2, 4, 6, 8, 10, 12, 14, 16, 18]
# [3, 6, 9, 12, 15, 18, 21, 24, 27]
# [4, 8, 12, 16, 20, 24, 28, 32, 36]
# [5, 10, 15, 20, 25, 30, 35, 40, 45]
分解ポイント
- 外側の for x in range(1, 6) が 行(段) の生成
- 内側の [x * y for y in range(1, 10)] が 列(1〜9) の各要素
- 結果は「リストのリスト」=2 次元データ
応用:見やすく整形して表示
for row in multbl:
print(' '.join(f'{n:>2}' for n in row))
- f'{n:>2}’ は 幅2で右寄せ(桁がそろって見やすい)
使い分けの勘所(今回の追補で出てきた観点)
_は「値を使いません」の合図:読み手に親切で、リンター警告も避けやすい- 文字 ↔ コードポイント:chr/ord で往復できる。ASCII 限定なら ord(‘a’) バイアスがシンプル
- enumerate と
zip:タプルで“構造を持ったまま”加工できる。enumerate(…, start=1) や zip_longest の使い分けも覚えると強い - 多重内包:順序は 左から右が外→内。読みづらいと感じたら 一度 for 文に戻して から再内包化すると安全
- 2 次元生成:外側=行、内側=列、と言語化しておくと迷わない





