コンテナ比較と包括的な内包表記
リスト、タプル、辞書、セットを個別に学んできました。今回は横並びで比較します——それぞれの違いは何か、いつどれを使うべきか。また、Python の最も強力な機能の 1 つである内包表記も徹底的にマスターします。
1. 4 つのコンテナの比較
| 機能 | リスト(list) |
タプル(tuple) |
辞書(dict) |
セット(set) |
|---|---|---|---|---|
| 構文 | [] |
() |
{key: value} |
{} または set() |
| 順序付け | ✅ あり | ✅ あり | ✅ 挿入順(3.7+) | ❌ なし |
| ミュータブル | ✅ | ❌ | ✅ | ✅ |
| 重複 | ✅ 許可 | ✅ 許可 | キーは一意、値は重複可能 | ❌ ユニーク |
| アクセス | インデックス [0] |
インデックス [0] |
キー ["key"] |
インデックス不可 |
| 検索速度 | O(n) 低速 | O(n) 低速 | O(1) 非常に高速 | O(1) 非常に高速 |
| 典型的な用途 | 順序データ、動的コレクション | 固定データ、関数の戻り値 | マッピング、キャッシュ | 重複除去、集合演算 |
💡 重要な選択原則: 1)位置によるアクセスが必要 → リスト/タプル。2)名前によるアクセスが必要 → 辞書。3)重複除去や集合演算が必要 → セット。4)データが変更されない → タプル。
例:コンテナ選択の比較(難易度 ⭐⭐)
PYTHON
# 同じシナリオを異なるコンテナで実装
# シナリオ:学生名の保存
# リスト — 順序付けられ、追加/削除が容易
students_list = ["Alice", "Bob", "Charlie"]
students_list.append("Diana")
print(f"List: {students_list[0]}") # Alice
# タプル — 固定(クラス名簿など)
students_tuple = ("Alice", "Bob", "Charlie")
# students_tuple[0] = "xxx" # エラー!タプルはイミュータブル
print(f"Tuple: {students_tuple[0]}") # Alice
# 辞書 — ID による高速検索
students_dict = {
"001": "Alice",
"002": "Bob",
"003": "Charlie",
}
print(f"Dict: {students_dict['002']}") # Bob — O(1) の速度
# セット — 重複除去(出席リストなど)
attendance = {"Alice", "Bob", "Charlie", "Alice"}
print(f"Set: {attendance}") # {'Bob', 'Charlie', 'Alice'} — Alice は重複除去
2. 高度なリスト内包表記
レッスン 11 では基本的な内包表記を学びました。さらに深く掘り下げましょう。
複数条件
PYTHON
numbers = range(1, 31)
# 3 で割り切れ、かつ 5 で割り切れる
filtered = [n for n in numbers if n % 3 == 0 and n % 5 == 0]
print(filtered) # [15, 30]
# 3 で割り切れる、または 5 で割り切れる
filtered = [n for n in numbers if n % 3 == 0 or n % 5 == 0]
print(filtered) # [3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30]
ネストしたループの内包表記
PYTHON
# 座標ペアを生成 — 二重 for ループに相当
pairs = [(x, y) for x in range(3) for y in range(3)]
print(pairs)
# [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
# 条件付きネスト — 和が偶数のペアを見つける
pairs = [(x, y) for x in range(3) for y in range(3) if (x + y) % 2 == 0]
print(pairs)
# [(0, 0), (0, 2), (1, 1), (2, 0), (2, 2)]
内包表記での if-else
PYTHON
# 内包表記での if-else — 位置が異なることに注意
numbers = [1, 2, 3, 4, 5, 6]
# if のみ(フィルタリング)— 後ろに配置
evens = [n for n in numbers if n % 2 == 0]
# if-else(変換)— 前に配置
result = ["even" if n % 2 == 0 else "odd" for n in numbers]
print(result) # ['odd', 'even', 'odd', 'even', 'odd', 'even']
例:データ変換パイプライン(難易度 ⭐⭐⭐)
PYTHON
# 1 行で:フィルタ → 変換 → フォーマット
raw = [" apple ", "BANANA", "", " CHERRY", None, " date "]
# None と空文字列を除外 → 空白を除去 → 先頭大文字
cleaned = [item.strip().capitalize() for item in raw if item and item.strip()]
print(cleaned) # ['Apple', 'Banana', 'Cherry', 'Date']
出力:
TEXT
['Apple', 'Banana', 'Cherry', 'Date']
3. 辞書内包表記
辞書でも内包表記が使えます。構文はリストと似ていますが、{} を使用し、キーと値のペアを指定します:
PYTHON
# 基本的な辞書内包表記
squares = {x: x ** 2 for x in range(5)}
print(squares) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# 条件付き
even_squares = {x: x ** 2 for x in range(10) if x % 2 == 0}
print(even_squares) # {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}
# キーと値の反転
original = {"a": 1, "b": 2, "c": 3}
reversed_dict = {value: key for key, value in original.items()}
print(reversed_dict) # {1: 'a', 2: 'b', 3: 'c'}
実践:2 つのリストを辞書にマージ
PYTHON
# 2 つのリストを辞書にマージ
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
result = {names[i]: scores[i] for i in range(len(names))}
print(result) # {'Alice': 85, 'Bob': 92, 'Charlie': 78}
# zip() を使うとよりエレガント
result = {name: score for name, score in zip(names, scores)}
print(result) # {'Alice': 85, 'Bob': 92, 'Charlie': 78}
4. セット内包表記
セット内包表記は {} を使って式のみを記述します(コロンはなし)——辞書内包表記との違いはコロンの有無です:
PYTHON
# セット内包表記 — 自動重複除去
numbers = [1, 2, 2, 3, 3, 3, 4, 5, 5]
unique_squares = {x ** 2 for x in numbers}
print(unique_squares) # {1, 4, 9, 16, 25}
# 条件付き
even_set = {x for x in range(20) if x % 2 == 0}
print(even_set) # {0, 2, 4, 6, 8, 10, 12, 14, 16, 18}
💡 3 つの区別:
[x for x in ...] はリスト——順序付けられ、重複可能。{x: y for x in ...} は辞書——コロンあり。{x for x in ...} はセット——コロンなし、順序なし、重複除去。
例:単語分析(難易度 ⭐⭐⭐)
PYTHON
# 記事の語彙を分析
article = """
Python is great. Python is powerful!
I love Python. JavaScript is also great.
But Python is my favorite.
"""
# すべての単語を抽出
words = article.lower().split()
# セット内包表記で重複除去
unique_words = {word.strip(".!,") for word in words}
print(f"Total words: {len(words)}")
print(f"Unique words: {len(unique_words)}")
print(f"All unique words: {sorted(unique_words)}")
出力:
TEXT
Total words: 18
Unique words: 10
All unique words: ['also', 'but', 'favorite', 'great', 'is', 'javascript', 'love', 'my', 'python', 'i']
よくあるユースケース
- データクリーニングパイプライン:リスト内包表記で空の値をフィルタリングし、空白を除去し、フォーマットを 1 行で変換
- 辞書マッピングの反転:辞書内包表記でキーと値を入れ替えて逆引き用に
- 重複除去カウント:セット内包表記でユニーク要素の高速統計
- 条件付きグループ化:if-else 内包表記でデータを 2 つのグループに分割(「合格/不合格」など)
- 座標生成:ネスト内包表記でチェス盤の座標やピクセルグリッドなどを生成
❓ よくある質問
Q リスト内包表記とジェネレータ式の違いは何ですか?
A リスト内包表記
[x for x in range(10)] はすべてのデータを一度にメモリに生成します。ジェネレータ式 (x for x in range(10)) は遅延評価で、一度に 1 つの値——メモリを節約します。大規模データセット(数万以上)ではジェネレータを使用してください。⚠️ Q:内包表記のネストが 2 レベルを超える場合はどうすればよいですか?
A:2 レベルを超える内包表記は読みにくくなります。
[x for xs in matrix for ys in xs for x in ys] のようなコードは一目ではほとんど理解できません。3 レベル以上必要な場合は、通常の for ループかヘルパー関数を使用してください。
Q どのコンテナを使うか、どうやって決めればよいですか?
A 3 つの質問:① ユニークなキーが必要か?→ 辞書。② 重複除去や集合演算が必要か?→ セット。③ データを変更する必要があるか?→ 変更するならリスト、しないならタプル。ほとんどのシナリオでは、リストと辞書で 90% のユースケースをカバーできます。
📖 まとめ
- リストは順序付けられミュータブル。タプルは順序付けられイミュータブル。辞書はキーと値で高速検索。セットは重複除去と集合演算用
- リスト内包表記:
[expression for variable in iterable if condition] - 辞書内包表記:
{key: value for variable in iterable if condition} - セット内包表記:
{expression for variable in iterable if condition} - ループの後の
ifはフィルタリング用。ループの前のif-elseは変換用 - ネスト内包表記で 2D データを生成できるが、2 レベルを超える場合は通常のループを使用
📝 練習問題
-
基本(難易度 ⭐):リスト内包表記を使って、10 から 50 までの 7 で割り切れるか 7 を含む数をすべて生成してください。ヒント:
orで 2 つの条件を結合。 -
中級(難易度 ⭐⭐):2 つのリスト
keys = ["name", "age", "city"]とvalues = ["Alice", 25, "Beijing"]が与えられたとき、辞書内包表記を使ってマージしてください。ヒント:zip(keys, values)でペアにする。 -
挑戦(難易度 ⭐⭐⭐):「データ分析プログラム」を書いてください。複数の文を含む文字列が与えられたとき、総単語数、ユニーク単語数、最も頻繁な 3 つの単語をカウントして出力します。ヒント:トークン化に
split()、カウントに辞書、上位 3 つにsorted(dict.items(), key=lambda x: x[1], reverse=True)[:3]。



