Pythonのsetは、重複排除や要素の高速な所属判定に優れたデータ構造です。
本記事では、和集合(union)と積集合(intersection)を中心に、演算子とメソッドの違い、空集合の作り方、型の注意点、frozensetの活用までを丁寧に解説します。
読みやすさと実行結果の安定性を意識しながら、実用的なサンプルで理解を深めます。
setで和集合・積集合を扱う基礎
setの特徴(重複なし・順序なし)
setは要素の重複を許さず、順序を保持しません。
そのため、同じ値を複数回追加しても1つにまとまり、表示順序は実行ごとに異なる場合があります。
メンバーシップ判定(要素が含まれるか)が高速で、集合演算に向いています。
# 重複が自動的に取り除かれることを確認する
s = {1, 2, 2, 3, 3, 3}
print(s) # 表示順序は保証されない
print(2 in s) # 所属判定は高速
print(len(s)) # ユニーク要素数
# 表示順を安定させたい場合は sorted で並べ替える
print(sorted(s))
{1, 2, 3}
True
3
[1, 2, 3]
補足
setはミュータブル(変更可能)です。
後述のupdateやintersection_updateは集合をその場で更新します。
- 関連記事:in演算子は遅い? リストとセット/辞書の計算量比較
- 関連記事:リストの重複を削除するsetの使い方
- 関連記事:もう迷わない!リストを昇順・降順にソートする
- 組み込み型 — Python 3.13.7 ドキュメント
空集合の作り方(set)と要素(ハッシュ可能)
{}は辞書(dict)を表すため、空集合は必ずset()で作ります。
また、setの要素はハッシュ可能(不変)である必要があります。
例えばintやstr、tuple、frozensetはOKですが、listやdictはNGです。
# 空集合の正しい作り方
empty_set = set()
print(type(empty_set))
# {} は空の辞書になる
empty_braces = {}
print(type(empty_braces))
# ハッシュ可能な要素はOK
ok = {1, "a", (10, 20)} # tupleは不変なのでOK
print(ok)
# ハッシュ不可能な要素はエラー
try:
ng = {1, [2, 3]} # listは不変ではないためNG
except TypeError as e:
print("TypeError:", e)
<class 'set'>
<class 'dict'>
{1, 'a', (10, 20)}
TypeError: unhashable type: 'list'
和集合の作り方(union, |)
unionの基本構文と戻り値(非破壊)
和集合は「いずれかの集合に含まれる要素の全体」です。
set.unionは新しい集合を返し、元の集合は変更しません。
任意のイテラブルを受け取れるのも特徴です。
# union は新しい set を返す(非破壊)
a = {1, 2}
b = {2, 3}
u = a.union(b) # a, b は変化しない
print("u:", sorted(u))
print("a:", sorted(a), "b:", sorted(b))
# イテラブル(例: list)も渡せる
u2 = a.union([3, 4, 4])
print("u2:", sorted(u2))
u: [1, 2, 3]
a: [1, 2] b: [2, 3]
u2: [1, 2, 3, 4]
- 関連記事:辞書内包表記/セット内包表記に入門する
|演算子の使い方と可読性
|演算子でも和集合を作れます。
短く書けて読みやすい一方で、右側はset系型である必要があります。
リストなどと組み合わせる場合はunionを使います。
a = {1, 2}
b = {2, 3}
# 演算子での和集合
print(sorted(a | b))
# 右側が set でないと TypeError
try:
print(a | [3, 4]) # list は不可
except TypeError as e:
print("TypeError:", e)
[1, 2, 3]
TypeError: unsupported operand type(s) for |: 'set' and 'list'
複数集合の和集合
複数の集合をまとめたい場合、unionは可変長引数に対応しています。
演算子は連結できますが、読みやすさの観点ではメソッドで一気に渡す方法が明瞭です。
a = {1, 2}
b = {2, 3}
c = {3, 4}
# メソッドでまとめて
u1 = a.union(b, c)
print("union:", sorted(u1))
# 演算子を連結
u2 = a | b | c
print("op:", sorted(u2))
union: [1, 2, 3, 4]
op: [1, 2, 3, 4]
update(|=)でその場で和集合
集合をその場で更新したい場合はupdateや|=を使います。
メモリ効率が良く、大きな集合で有利です。
updateは任意のイテラブルを受け付けます。
a = {1, 2}
b = {2, 3}
# メソッドでのインプレース更新
a.update(b) # a が更新される
print("a after update:", sorted(a))
# 演算子でのインプレース更新
a |= {4, 5} # さらに a を更新
print("a after |= :", sorted(a))
a after update: [1, 2, 3]
a after |= : [1, 2, 3, 4, 5]
積集合の作り方(intersection, &)
intersectionの基本構文と戻り値(非破壊)
積集合は「全ての集合に共通する要素」です。
set.intersectionは新しい集合を返し、任意のイテラブルを受け取れます。
a = {1, 2, 3}
b = {2, 3, 4}
i = a.intersection(b) # a, b は変化しない
print("i:", sorted(i))
# イテラブル(例: list)も渡せる
i2 = a.intersection([2, 99])
print("i2:", sorted(i2))
i: [2, 3]
i2: [2]
&演算子の使い方と注意点
&演算子は積集合を簡潔に書けますが、右側はset系型である必要があります。
リストなどとの積集合はintersectionを用います。
a = {1, 2, 3}
b = {2, 3, 4}
print(sorted(a & b)) # OK
try:
print(a & [2, 3]) # list は不可
except TypeError as e:
print("TypeError:", e)
[2, 3]
TypeError: unsupported operand type(s) for &: 'set' and 'list'
複数集合の積集合
複数集合の積集合もintersectionでまとめて指定できます。
演算子は連結も可能です。
a = {1, 2, 3, 4}
b = {2, 3, 5}
c = {0, 2, 3, 9}
# メソッドでまとめて
i1 = a.intersection(b, c)
print("intersection:", sorted(i1))
# 演算子で連結
i2 = a & b & c
print("op:", sorted(i2))
intersection: [2, 3]
op: [2, 3]
intersection_update(&=)でその場で積集合
その場で更新する場合はintersection_updateや&=を使います。
大規模データで特に有効です。
a = {1, 2, 3, 4}
b = {2, 3, 5}
a.intersection_update(b) # a が共通要素のみになる
print("a after intersection_update:", sorted(a))
a &= {2} # さらに a を絞り込む
print("a after &= :", sorted(a))
a after intersection_update: [2, 3]
a after &= : [2]
つまずきやすい注意点とベストプラクティス
並び順は保証されない(表示順に注意)
setは順序を保持しないため、printの表示順は保証されません。
テストやログで安定した表示が必要な場合はsortedで並べ替えて出力しましょう。
s = {"b", "a", "c"}
print("raw:", s) # 順序は不定
print("sorted:", sorted(s)) # 安定した順序
raw: {'b', 'a', 'c'}
sorted: ['a', 'b', 'c']
空集合との演算結果(和集合/積集合)
空集合との演算は覚えておくと便利です。
和集合は元の集合のまま、積集合は常に空集合になります。
s = {1, 2, 3}
empty = set()
print("union with empty:", sorted(s.union(empty)))
print("intersection with empty:", s.intersection(empty)) # 空集合
union with empty: [1, 2, 3]
set()
型の混在と比較の注意
Pythonでは同値とみなされる異種型があります。
例えば1 == Trueや1 == 1.0はTrueのため、setでは重複として扱われ1つにまとまります。
意図しない重複除去を避けるため、型を統一しましょう。
s = {1, True, 1.0, 0, False}
print(s) # 1 と True、0 と False は同一とみなされる
print(len(s)) # 要素数に注意
# NaN は自分自身と等しくないため重複しない例
import math
t = {float('nan'), float('nan')}
print(t, len(t)) # 2 つ入ることがある(実装依存のハッシュだが == が False のため区別される)
{0, 1.0}
2
{nan, nan} 2
frozensetの使いどころ(辞書キー)
frozensetは不変の集合で、辞書のキーやsetの要素にできます。
ネストした集合を扱う場合に便利です。
# frozenset を set の要素にする(ネスト)
inner1 = frozenset({1, 2})
inner2 = frozenset({2, 3})
outer = {inner1, inner2}
print(outer)
# frozenset を辞書キーにする
paths = {
frozenset({"A", "B"}): 10,
frozenset({"B", "C"}): 20,
}
print(paths[frozenset({"B", "A"})]) # 順序に依存しないキー
{frozenset({1, 2}), frozenset({2, 3})}
10
演算子かメソッドか(読みやすさと速度)
基本方針として、相手がset系なら演算子、イテラブルをそのまま渡したいならメソッドが読みやすいです。
パフォーマンス差は通常ごく小さく、インプレース更新(update系)はメモリ効率に優れます。
下表は違いの要点です。
| 操作 | 非破壊演算子 | 非破壊メソッド | その場更新 | 相手が任意イテラブル | ||
|---|---|---|---|---|---|---|
| 和集合 | | union | | = / update | メソッドは可、演算子は不可 | |
| 積集合 | & | intersection | &= / intersection_update | メソッドは可、演算子は不可 |
a = {1, 2}
# 相手が list のときはメソッドで
u = a.union([2, 3, 4]) # OK
print(sorted(u))
# 大きな集合を効率よく更新したいときは in-place
a.update({5, 6}) # 余計なコピーを作らない
print(sorted(a))
[1, 2, 3, 4]
[1, 2, 5, 6]
まとめ
本記事では、setでの和集合と積集合の基本から、演算子|と&、メソッドunionとintersection、さらにupdate系のインプレース更新までを体系的に解説しました。
相手がset系なら演算子、任意イテラブルならメソッドという指針を持つと、読みやすく安全なコードになります。
表示順は保証されない点、空集合との演算、型の同値性(1とTrueなど)やfrozensetの活用も押さえておくと、実務でのバグを避けやすくなります。
実装ではテスト出力にsortedを使って安定化し、大規模集合ではインプレース更新でメモリ効率を確保すると良いでしょう。
