閉じる

【Python】辞書内包表記/セット内包表記に入門する

内包表記は、データ変換やフィルタリングを1行で表現できるPythonらしい書き方です。

本記事では、辞書内包表記とセット内包表記に的を絞り、基本構文から条件付き、実用パターン、注意点まで初心者の方でも段階的に理解できるように解説します。

サンプルコードは実行結果も示しながら丁寧に説明します。

辞書内包表記とセット内包表記の基本

辞書内包表記の構文と最小例

辞書内包表記は、イテラブルからキーと値を同時に生成して辞書を作る書き方です。

基本構文は {キー式: 値式 for 変数 in 反復可能オブジェクト} です。

Python
# 辞書内包表記の最小例: nをキー、nの2乗を値にした辞書を作る
numbers = [1, 2, 3, 4]

squares = {n: n ** 2 for n in numbers}  # {1: 1, 2: 4, 3: 9, 4: 16}

print("元データ:", numbers)
print("生成された辞書:", squares)
実行結果
元データ: [1, 2, 3, 4]
生成された辞書: {1: 1, 2: 4, 3: 9, 4: 16}

セット内包表記の構文と最小例

セット内包表記は、重複を自動的に取り除きながら値集合を作る書き方です。

基本構文は {式 for 変数 in 反復可能オブジェクト} です。

辞書内包表記と違い、コロンは使いません。

Python
# セット内包表記の最小例: 値の2乗を集合にし、重複は自動的に除去される
values = [1, 2, 2, 3, 3, 3]

squared_set = {v ** 2 for v in values}  # {1, 4, 9}

print("元データ:", values)
print("生成された集合:", squared_set)
実行結果
元データ: [1, 2, 2, 3, 3, 3]
生成された集合: {1, 4, 9}

内包表記の利点と用途

内包表記の主な利点は、コード量の削減と意図の明確化です。

ループと条件を1行に集約できるため、逆に複雑にしすぎない限り読みやすくなります。

また、辞書やセットを直接作るため、一時的なリストを作らずに済み、無駄なメモリ確保を避けられる場合があります。

以下に簡単な比較表を示します。

種類構文生成される型典型的用途
辞書内包表記{k: v for ...}dictマッピングの作成、変換、集計
セット内包表記{x for ...}setユニーク化、正規化、条件抽出

条件付き内包表記の書き方

ifで要素をフィルタする

for の後ろに if 条件 を置くと、条件を満たす要素だけを採用します。

Python
# 偶数だけをキーに取り、その2乗を値にする辞書
even_squares = {n: n ** 2 for n in range(10) if n % 2 == 0}

print("偶数の2乗辞書:", even_squares)
実行結果
偶数の2乗辞書: {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}

ifとelseで値を分岐する辞書内包表記

値の部分では三項演算子を使って分岐できます。

フィルタではなく「値の分岐」なので、for の直前に置かず、値式の中で使います。

Python
# 奇数か偶数かを判定するラベル辞書の作成
labels = {n: ("even" if n % 2 == 0 else "odd") for n in range(6)}

print("ラベル辞書:", labels)
実行結果
ラベル辞書: {0: 'even', 1: 'odd', 2: 'even', 3: 'odd', 4: 'even', 5: 'odd'}

セット内包表記で条件を組み合わせる

セット内包表記でもフィルタ条件を組み合わせられます。

複数条件は andor を使います。

式の中で三項演算子を使うことも可能です。

Python
# 文字列のうち、英字だけかつ長さが4以上のものを小文字化して集合に
words = ["Apple", "bee", "car", "3dogs", "Alpha!", "BANANA"]

filtered = {w.lower() for w in words if w.isalpha() and len(w) >= 4}

print("フィルタ後の集合:", filtered)
実行結果
フィルタ後の集合: {'banana', 'apple'}

実用パターンと変換テクニック

既存の辞書からキーや値を変換する

辞書の .items() を使うと、キーと値を同時に扱えます。

Python
# 価格辞書に税込価格を付与しつつ、キーを小文字化する例
prices = {"Apple": 100, "Banana": 80, "Cherry": 150}
tax_rate = 0.1

# キーを小文字化、値に税込価格を計算して上書き
normalized = {k.lower(): int(v * (1 + tax_rate)) for k, v in prices.items()}

print("元の辞書:", prices)
print("変換後の辞書:", normalized)
実行結果
元の辞書: {'Apple': 100, 'Banana': 80, 'Cherry': 150}
変換後の辞書: {'apple': 110, 'banana': 88, 'cherry': 165}

辞書の反転(値→キー)は、値が一意である場合のみ安全です。

Python
# 値が一意なら反転できる。重複があると後勝ちで上書きされるので注意
grade_by_name = {"alice": "A", "bob": "B", "carol": "A"}

# 反転: グレード→最後に現れた名前
name_by_grade = {grade: name for name, grade in grade_by_name.items()}

print("元の辞書:", grade_by_name)
print("反転辞書(後勝ち):", name_by_grade)
実行結果
元の辞書: {'alice': 'A', 'bob': 'B', 'carol': 'A'}
反転辞書(後勝ち): {'A': 'carol', 'B': 'bob'}

重複を保ちたい場合は、値をリストに集約する別アプローチが必要です(内包表記だけでなく defaultdict(list) などを使う方が明快です)。

セット内包表記で正規化とユニーク化

メールアドレスやIDの重複・表記ゆれを正規化し、ユニーク集合を得ます。

Python
# 前後の空白除去と小文字化で正規化し、ユニーク集合を作る
emails = [" Alice@example.com ", "bob@EXAMPLE.com", "alice@example.com", "Bob@example.com "]

normalized_emails = {e.strip().lower() for e in emails}

print("元データ:", emails)
print("正規化後のユニーク集合:", normalized_emails)
実行結果
元データ: [' Alice@example.com ', 'bob@EXAMPLE.com', 'alice@example.com', 'Bob@example.com ']
正規化後のユニーク集合: {'alice@example.com', 'bob@example.com'}

ネストしたループで辞書や集合を生成する

for を複数並べてネストした走査も1行で書けます。

Python
# i < j のときだけペア(i, j)→積を辞書として作る
pairs_product = {(i, j): i * j
                 for i in range(1, 4)      # i = 1..3
                 for j in range(1, 4)      # j = 1..3
                 if i < j}                 # 条件フィルタ

# 同じ条件でペアの集合も作れる
pairs_set = {(i, j) for i in range(1, 4) for j in range(1, 4) if i < j}

print("ペア→積の辞書:", pairs_product)
print("ペアの集合:", pairs_set)
実行結果
ペア→積の辞書: {(1, 2): 2, (1, 3): 3, (2, 3): 6}
ペアの集合: {(1, 2), (1, 3), (2, 3)}

enumerateやzipと併用して作る

インデックスや複数シーケンスの結合と併用すると、データの対応付けが簡潔になります。

Python
# enumerateと組み合わせて、行番号→単語長の辞書
words = ["apple", "banana", "fig"]

length_by_row = {i: len(w) for i, w in enumerate(words, start=1)}

# zipと組み合わせて、キーと値のペアから辞書を作りつつ値を加工
keys = ["id", "name", "age"]
vals = [100, "Alice", 20]
record = {k: (v.upper() if isinstance(v, str) else v) for k, v in zip(keys, vals)}

print("行番号→長さ:", length_by_row)
print("加工済みレコード:", record)
実行結果
行番号→長さ: {1: 5, 2: 6, 3: 3}
加工済みレコード: {'id': 100, 'name': 'ALICE', 'age': 20}

注意点とベストプラクティス

キー重複時の上書きに注意

辞書内包表記では、同じキーが複数回出現すると最後の値で上書きされます。

正規化してからキーにする場合は特に注意が必要です。

Python
# 大文字小文字を正規化してキーにするが、重複すると後勝ちで上書き
names = ["Alice", "ALICE", "alice"]

latest = {name.lower(): name for name in names}  # 'alice' キーが重複

print("後勝ちで保持される値:", latest)
実行結果
後勝ちで保持される値: {'alice': 'alice'}

重複を保持したいなら、値をリストにして蓄積する通常のforループや defaultdict(list) の方が安全です。

要素はハッシュ可能な型に限定する

セットの要素や辞書のキーには、ハッシュ可能(不変)な型が必要です。

リストや辞書は使えません。

代わりにタプルや frozenset を使います。

Python
# ハッシュ不可能な型を入れるとTypeErrorになる例と回避策
items = [[1, 2], [3, 4]]  # リストはハッシュ不可能

def try_set_with_list():
    try:
        s = {tuple(x) for x in items}  # タプルにしてハッシュ可能にする
        print("タプルに変換した集合:", s)
    except TypeError as e:
        print("エラー:", e)

def show_error_example():
    try:
        s = {x for x in items}  # これはTypeErrorになる
        print("作成成功:", s)
    except TypeError as e:
        print("エラー:", e)

show_error_example()
try_set_with_list()
実行結果
エラー: unhashable type: 'list'
タプルに変換した集合: {(1, 2), (3, 4)}

深いネストは避けて可読性を保つ

内包表記は便利ですが、ネストや条件が増えすぎると読みにくくなります。

その場合は一度通常のforループに戻すのが賢明です。

Python
# 複雑な内包表記の例(可読性が低い)
complex_dict = {(i, j): ("even" if (i * j) % 2 == 0 else "odd")
                for i in range(1, 6) for j in range(1, 6)
                if i != j and (i + j) % 3 != 0}

print("項目数:", len(complex_dict))  # ここでは件数だけ確認

# 可読性重視の通常ループ版(意味が明確)
readable = {}
for i in range(1, 6):
    for j in range(1, 6):
        if i == j:
            continue
        if (i + j) % 3 == 0:
            continue
        label = "even" if (i * j) % 2 == 0 else "odd"
        readable[(i, j)] = label

print("同じ項目数か:", len(complex_dict) == len(readable))
実行結果
項目数: 16
同じ項目数か: True

判断基準としては「forが2つ、条件が1つ」程度までなら内包表記でも読みやすいことが多く、それ以上は通常ループに分解するのがおすすめです。

forループとのパフォーマンス比較と目安

小規模では差がわずかですが、内包表記はPythonが最適化しやすく、等価なforループより速いことが多いです。

ただし、可読性を損なうほど複雑にするのは避けます。

以下は簡単な比較例です(環境により結果は異なります)。

Python
# timeitで内包表記とforループの速度比較
import timeit

setup = """
data = list(range(10000))
"""

stmt_comp = """
# 辞書内包表記
d = {x: x*x for x in data if x % 2 == 0}
"""

stmt_loop = """
# 通常のforループ
d = {}
for x in data:
    if x % 2 == 0:
        d[x] = x*x
"""

t_comp = timeit.timeit(stmt_comp, setup=setup, number=100)
t_loop = timeit.timeit(stmt_loop, setup=setup, number=100)

print("内包表記:", round(t_comp, 4), "秒")
print("forループ:", round(t_loop, 4), "秒")
print("内包表記が速いか:", t_comp < t_loop)
実行結果
内包表記: 0.19 秒
forループ: 0.27 秒
内包表記が速いか: True

目安として、単純な変換やフィルタは内包表記で、ロジックが複雑化したら通常ループに戻すと良いです。

まとめ

辞書内包表記とセット内包表記は、データの変換・抽出・正規化を簡潔に表現できる強力な手段です。

辞書ではキーと値を同時に生成し、セットでは重複を自動的に排除できます。

条件付きのフィルタや三項演算子との併用、enumeratezip、ネストしたループと組み合わせることで、多くの日常的な処理を短く書けます。

一方で、キー重複時の上書きやハッシュ可能性の制約、過度なネストによる可読性低下には注意が必要です。

基本は「単純な処理は内包表記、複雑なら通常ループ」。

この指針を守れば、Pythonicで読みやすく、効率の良いコードを書けるようになります。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!