リスト内包表記が理解できたら、もう二度とリストに追加するforループを書かなくなります。
これは単なる糖衣構文ではありません — 意図を明確に伝え、バイトコードの最適化のおかげでCPythonの同等のループより速く実行されます。
公式Pythonチュートリアルによれば、
内包表記は既存のシーケンスや他のイテラブルに基づいてリストを作成する簡潔な方法を提供します。
この記事では最初にメンタルモデルを構築し、次にすべての実際のパターンをカバーします:フィルタリング、ネスト、
dictとsetの内包表記、そして代わりに普通のforループを使うべき唯一のケース。
基本パターン
リスト内包表記の構造は[expression for item in iterable]です。
3つの部分:出力式(各要素が何になるか)、ループ変数、
そして引き出すイテラブル。馴染みのあるループから始めて折り畳んでください。
# Plain for loop — building a list of file sizes in KB
file_sizes_bytes = [1024, 204800, 51200, 3145728, 8192]
sizes_kb = []
for size in file_sizes_bytes:
sizes_kb.append(size / 1024)
# print(sizes_kb) → [1.0, 200.0, 50.0, 3072.0, 8.0]
# Same result as a list comprehension — one line, same meaning
sizes_kb = [size / 1024 for size in file_sizes_bytes]内包表記はほとんど英語のように読めます:「file_sizes_bytesの各sizeについてsize / 1024をください」。その明確さが本当の利点です — 読者は何を構築しているかを理解するためにappend呼び出しをトレースする必要がありません。
# Another common pattern: deriving one list from another
usernames = ["alice", "bob", "carol"]
# Build a list of display names
display_names = [name.capitalize() for name in usernames]
# ['Alice', 'Bob', 'Carol']
# Or extract a single field from a list of dicts
users = [
{"id": 1, "name": "Alice", "role": "admin"},
{"id": 2, "name": "Bob", "role": "viewer"},
{"id": 3, "name": "Carol", "role": "editor"},
]
names = [user["name"] for user in users]
# ['Alice', 'Bob', 'Carol']ifによるフィルタリング
最後に条件を追加すると、条件を通過する要素だけが出力リストに入ります:
[expression for item in iterable if condition]。
これがfor + if + appendの組み合わせを置き換えるパターンです。
# Loop version — extract only active users
active_users = []
for user in users:
if user["active"]:
active_users.append(user["name"])
# Comprehension version — identical result
users = [
{"name": "Alice", "active": True},
{"name": "Bob", "active": False},
{"name": "Carol", "active": True},
{"name": "Dave", "active": False},
]
active_users = [user["name"] for user in users if user["active"]]
# ['Alice', 'Carol']# Filtering a raw CSV row — drop blanks and whitespace-only values
raw_row = ["[email protected]", "", " ", "editor", " "]
clean_row = [field.strip() for field in raw_row if field.strip()]
# ['[email protected]', 'editor']
# Filtering a list of log levels
log_lines = [
"INFO server started",
"DEBUG loading config",
"ERROR database timeout",
"DEBUG query took 450ms",
"ERROR disk space low",
]
errors = [line for line in log_lines if line.startswith("ERROR")]
# ['ERROR database timeout', 'ERROR disk space low']文字列の処理
文字列処理は内包表記が真価を発揮する場所です。Pythonの豊富な 文字列メソッドと内包表記構文の組み合わせは、中間変数なしに変換パイプラインを読みやすく保ちます。
# Strip whitespace from tags coming out of a form field
raw_tags = [" python ", "data science", " machine-learning ", "API"]
tags = [tag.strip().lower() for tag in raw_tags]
# ['python', 'data science', 'machine-learning', 'api']
# Normalise email addresses from a signup CSV
raw_emails = ["[email protected]", " [email protected] ", "[email protected]"]
emails = [e.strip().lower() for e in raw_emails]
# ['[email protected]', '[email protected]', '[email protected]']
# Extract file extensions from a list of uploaded filenames
filenames = ["report.pdf", "avatar.PNG", "data.CSV", "archive.tar.gz", "notes.txt"]
extensions = [name.rsplit(".", 1)[-1].lower() for name in filenames if "." in name]
# ['pdf', 'png', 'csv', 'gz', 'txt']" [email protected] "のような値がコンテンツの.lower()チェックを通過しますが、データベース検索やJSONキーマッチングを壊す先頭のスペースを持ち続けます。dictとsetの内包表記
同じアイデアがdictとsetにも拡張されます。
dict内包表記
はkey: valueペアで波括弧を使います:{key: value for item in iterable}。
set内包表記はコロンを省いて重複を排除したコレクションを生成します:
{expression for item in iterable}。
# Invert a dict — swap keys and values
permissions = {"alice": "admin", "bob": "viewer", "carol": "editor"}
by_role = {role: name for name, role in permissions.items()}
# {'admin': 'alice', 'viewer': 'bob', 'editor': 'carol'}
# Build a fast lookup dict from a list of user records
users = [
{"id": 101, "name": "Alice", "active": True},
{"id": 102, "name": "Bob", "active": False},
{"id": 103, "name": "Carol", "active": True},
]
# O(1) lookups by ID — much faster than scanning the list every time
user_by_id = {user["id"]: user for user in users}
# {101: {...}, 102: {...}, 103: {...}}
# Access a user directly
user_by_id[102]["name"] # 'Bob'# Set comprehension — deduplicate a list of file extensions
uploads = ["report.pdf", "data.csv", "summary.pdf", "export.CSV", "notes.txt"]
unique_extensions = {name.rsplit(".", 1)[-1].lower() for name in uploads if "." in name}
# {'pdf', 'csv', 'txt'} — order not guaranteedsetの注意点:順序は保持されません。元の順序を保持しながらリストを重複排除する必要がある場合、set内包表記は間違ったツールです — 代わりにlist(dict.fromkeys(items))を使ってください。これはPython 3.7+でのdictの挿入順序動作を活用します。
ネストした内包表記
ネストした構造を反復処理するために内包表記をネストできます。最も一般的なユースケースは リストのリストを平坦化することです — CSVパースからの行列、チャンク化されたAPIレスポンスページ、またはグループ化されたクエリ結果。
# Flatten a 2D list (e.g. paginated API results)
pages = [
[{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}],
[{"id": 3, "name": "Carol"}],
[{"id": 4, "name": "Dave"}, {"id": 5, "name": "Eve"}],
]
all_users = [user for page in pages for user in page]
# [{'id': 1, ...}, {'id': 2, ...}, {'id': 3, ...}, {'id': 4, ...}, {'id': 5, ...}]
# The order mirrors what nested for loops would produce:
# for page in pages:
# for user in page:
# all_users.append(user)ネストした内包表記は左から右に読みます — 外側のループが最初、内側のループが2番目です。
これはネストしたforループの順序と一致します。
# Two levels — fine and readable
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [cell for row in matrix for cell in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Three levels — stop here. Use a plain loop or a helper function.
# This is the point where readability dies:
cube = [[[1,2],[3,4]],[[5,6],[7,8]]]
# Don't do this:
flat3 = [val for layer in cube for row in layer for val in row]
# Do this instead:
flat3 = []
for layer in cube:
for row in layer:
flat3.extend(row)(user["id"] for user in users)。遅延評価します — 完全なリストをメモリに構築するのではなく、要素を1つずつ生成します。1回だけ反復する場合、または結果をsum()、max()、any()、または類似の関数に直接渡す場合に使用してください。
詳細についてはジェネレータ式のリファレンスを参照してください。内包表記内のif/else三項演算子
要素を削除するのではなく、2つの値のうちの1つに変換する必要がある場合、 出力位置で三項式を使います。位置が重要です: 三項演算子は最初に属し、イテラブルの後ではありません。
# Correct: ternary is the output expression
users = [
{"name": "Alice", "active": True},
{"name": "Bob", "active": False},
{"name": "Carol", "active": True},
]
status = ["active" if user["active"] else "inactive" for user in users]
# ['active', 'inactive', 'active']
# Normalise a config value — replace None with a default
raw_config = ["/var/log", None, "/tmp", None, "/etc/app"]
paths = [path if path is not None else "/var/log/default" for path in raw_config]
# ['/var/log', '/var/log/default', '/tmp', '/var/log/default', '/etc/app']# Common mistake — putting the ternary after the iterable (SyntaxError)
# status = ["active" for user in users if user["active"] else "inactive"] # WRONG
# The trailing "if" is a filter — it drops non-matching items entirely.
# The ternary ("if ... else ...") in the output expression transforms all items.
# They serve different purposes. You can combine them:
# Keep only users, but show their status
status = ["active" if user["active"] else "inactive"
for user in users
if user["name"] != "Bob"]
# ['active', 'active'] — Bob was filtered out entirely代わりにforループを使うタイミング
リスト内包表記はリストを構築するためのものです。実際にやっていることが
副作用を実行することなら、内包表記ではなくforループを使ってください。これはスタイル以上の問題です:結果を捨てる内包表記は誰も使わないリストを構築してメモリを無駄にし、読者から意図を隠します。
# Bad — comprehension for side effects (writing to a file, printing, calling an API)
[print(f"Processing user {user['name']}") for user in users] # don't do this
[requests.post("/api/notify", json=user) for user in users] # definitely don't do this
# Good — plain for loop makes the intent obvious
for user in users:
print(f"Processing user {user['name']}")
for user in users:
requests.post("/api/notify", json=user)# Bad — comprehension with logic complex enough to need comments or multiple steps
result = [
user["name"].strip().lower()
if user.get("active") and user.get("email_verified")
else user["name"].strip().lower() + " (unverified)"
for user in users
if user.get("role") in ("admin", "editor") and user.get("last_login") is not None
]
# Good — break it out when the logic is this involved
result = []
for user in users:
if user.get("role") not in ("admin", "editor"):
continue
if user.get("last_login") is None:
continue
name = user["name"].strip().lower()
if not (user.get("active") and user.get("email_verified")):
name += " (unverified)"
result.append(name)- 内包表記を使うのは、明確な式とオプションのフィルタで既存のイテラブルから新しいリストを構築しているとき。
- forループを使うのは、副作用を実行しているとき — I/O、ネットワーク呼び出し、印刷、外部状態の変更。
- forループを使うのは、変換ロジックが理解するために複数行、中間変数、またはコメントを必要とするとき。
- ジェネレータ式を使うのは、1回だけ反復するか、
sum()、any()、max()に直接渡す場合 — メモリにリストは不要。
パフォーマンスの注意
リスト内包表記はCPythonの同等のfor + append
ループより意味のある速さで速いです。理由はバイトコードです:内包表記は専用のLIST_APPENDオペコードにコンパイルされ、各反復でlist.appendの属性検索を回避します。
Pythonパフォーマンスヒントwikiがこれを説明しており、リストサイズに応じて純粋なPythonワークロードで通常10〜40%の差があります。
import timeit
data = list(range(100_000))
# for + append
def with_loop():
result = []
for x in data:
result.append(x * 2)
return result
# list comprehension
def with_comprehension():
return [x * 2 for x in data]
# generator expression — no list built at all
def with_generator():
return sum(x * 2 for x in data)
# Typical results on CPython 3.12:
# with_loop(): ~7.2 ms
# with_comprehension(): ~4.8 ms (~33% faster)
# with_generator(): ~4.1 ms (and uses O(1) memory vs O(n))具体化されたリストが必要ない場合 — 結果をsum()、
any()、max()に渡すか、1回だけ反復する場合 — 代わりに
ジェネレータ式
を使ってください。入力サイズに関係なく一定のメモリを使用します。タイトなループで大きなCSVエクスポートやJSONペイロードを処理するときに重要です。
まとめ
リスト内包表記は約1週間ぎこちなく感じられ、その後なしでは生きられなくなるPython機能の1つです。メンタルモデルはシンプルです:出力式、ループ変数、イテラブル、オプションのフィルタ。それを守れば、読みやすくイディオマティックなPythonが書けます。ロジックが複雑になったら、普通のループに戻るサインです — 失敗ではなく、ただ仕事に適したツールを使うだけです。
PythonでJSONデータを扱っている場合 — APIレスポンスを値のリストに変換したり、レコードからフィールドを抽出したり、検索dictを構築したりする場合 — このサイトのツールは今学んだことと組み合わせて使えます。内包表記を書く前にJSONペイロードを検査して整形するにはJSONフォーマッターを試してください、またはCSVデータを行のリストにパースする前に検証するには CSVフォーマッターを使ってください。 完全な言語リファレンスは、 リスト内包表記に関するPythonドキュメント と PEP 202 (元の提案)を読む価値があります。