3.

Python セット(set)要素削除完全ガイド(remove / discard / pop / clear / 例外と返り値の違い)

編集
この記事の要点
  • Python の set から要素を削除するメソッドは 4 つ: remove() / discard() / pop() / clear()
  • remove(x) — x が無いと KeyError を投げる
  • discard(x) — x が無くてもエラーにならない(黙って何もしない)
  • pop() — 任意の要素を 1 つ取り出して返す。空 set なら KeyError
  • clear() — 全要素削除(set 自体は残る)

Python のセット(set)からの削除

Python の set 型は重複のない順序なしコレクションです。要素を 1 つ消すか全部消すかで使うメソッドが分かれます。本記事では remove / discard / pop / clear の使い分けと、よくある落とし穴をまとめます。

4 つの削除メソッドの比較表

メソッド用途要素が無い時戻り値
remove(x)x を削除KeyErrorNone
discard(x)x を削除(安全)何もしないNone
pop()任意の 1 要素を取り出す空なら KeyError削除した要素
clear()全削除空でも OKNone

remove() — 要素を指定して削除(厳格)

s = {"a", "b", "c"}
s.remove("b")
print(s)            # {'a', 'c'}

# 存在しない要素を remove すると KeyError
s.remove("z")       # KeyError: 'z'

remove は「絶対に存在するはず」のものを消すときに使います。存在を確信できない場合は discard または try / except で包みます。

# 安全に remove したいなら try/except か discard
s = {"a", "b", "c"}
try:
    s.remove("z")
except KeyError:
    pass

discard() — 要素を指定して削除(安全)

s = {"a", "b", "c"}
s.discard("b")
print(s)            # {'a', 'c'}

s.discard("z")      # エラーにならず黙って素通り
print(s)            # {'a', 'c'}

「あるかもしれないし無いかもしれない」要素を消したい時の定番。remove戻り値・処理コストはほぼ同じで、唯一の違いは存在しない場合に例外を投げるかどうかです。

pop() — 任意の 1 要素を取り出す

s = {"a", "b", "c"}
x = s.pop()
print(x, s)        # 例: 'a' {'b', 'c'}  ※どれが取り出されるかは不定

empty = set()
empty.pop()        # KeyError: 'pop from an empty set'

set は順序を持たないので、pop が返す要素は実行ごとに変わり得ます(実装上はハッシュ順で固定されますが、依存してはいけません)。「とりあえず 1 つ消費する」キュー的な用途で使います。

clear() — 全削除

s = {"a", "b", "c"}
s.clear()
print(s)           # set()
print(type(s))     # <class 'set'>  ※ set 自体は残る、空になるだけ

新しい空 set に置き換える s = set() とは違い、clear()同じオブジェクトを空にします。他の変数が同じ set を参照している場合、両方とも空になります。

a = {1, 2, 3}
b = a              # 同じオブジェクトを参照
a.clear()
print(b)           # set()  ← b も空になる

a = {1, 2, 3}
b = a
a = set()          # a だけ新オブジェクトに付け替え
print(b)           # {1, 2, 3}  ← b は影響なし

応用:差集合演算で複数要素を一気に削除

複数要素をまとめて削除したい場合は集合演算が便利です。

s = {"a", "b", "c", "d", "e"}

# 1) 差集合で削除(新しい set を返す)
result = s - {"a", "c", "z"}
print(result)            # {'b', 'd', 'e'}  ※ z が無くてもエラー無し

# 2) difference_update で in-place 削除
s.difference_update({"a", "c", "z"})
print(s)                 # {'b', 'd', 'e'}

# 3) リスト内包+discard で個別ループ
for x in ["a", "c", "z"]:
    s.discard(x)

反復中の削除に注意

set / dict / list をfor ループで反復している最中に要素を削除すると、Python はランタイムエラーまたは予期せぬスキップを起こします。

s = {1, 2, 3, 4, 5}

# NG: 反復中に変更
for x in s:
    if x % 2 == 0:
        s.discard(x)     # RuntimeError: Set changed size during iteration

正しくは「コピーを反復する」または「内包表記で新しい set を作る」のいずれかです。

# OK: スナップショットを反復
s = {1, 2, 3, 4, 5}
for x in list(s):
    if x % 2 == 0:
        s.discard(x)
print(s)                  # {1, 3, 5}

# OK: 新しい set を作る(推奨)
s = {1, 2, 3, 4, 5}
s = {x for x in s if x % 2 != 0}
print(s)                  # {1, 3, 5}

frozenset との違い

frozensetイミュータブルな set 型で、remove / discard / pop / clearすべて存在しません。要素を変えたい場合は新しい frozenset を作り直す必要があります。

fs = frozenset({"a", "b", "c"})
fs.remove("a")            # AttributeError: 'frozenset' object has no attribute 'remove'

# 代わりに集合演算で新オブジェクト
fs2 = fs - {"a"}
print(fs2)                # frozenset({'b', 'c'})

計算量の目安

操作平均計算量備考
remove / discardO(1)ハッシュテーブル探索
popO(1)任意の 1 要素
clearO(n)全要素を解放
difference_updateO(len(other))引き算側の長さに比例

FAQ

Q: remove と discard で速度差は?
A: ほぼ同じです。例外を投げるかどうかだけが違います。

Q: pop で順序が固定されているように見える
A: CPython の実装上は決まりますが、Python の言語仕様としては未定義です。ハッシュ順に依存するコードを書かないでください。

Q: 「該当があれば消す」を 1 行で書きたい
A: s.discard(x) がそれです。「存在チェック → 削除」を atomic に行えます。

関連

  • セット — 親カテゴリ
  • Python — 上位カテゴリ
  • 要素の追加(add / update) — 反対操作
  • frozenset — イミュータブル版
編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. セットの作成
  2. 要素の追加
  3. 要素の削除

最近更新/作成されたページ