Pythonのfrozensetとは?ミュータブルなsetとの違いをわかりやすく解説
生徒
「先生、Pythonのsetは知っていますが、frozensetって何ですか?setとどう違うんでしょう?」
先生
「frozensetは読み方は『フローズンセット』と言って、セットの仲間ですが、変更できない特徴があります。簡単にいうと、凍ったセットのようなものです。」
生徒
「変更できないセット?それはどういう意味ですか?」
先生
「Pythonのsetは、後から要素を追加したり削除したりできます。これを『ミュータブル』と言います。反対に、frozensetは作った後に要素を変えられません。これを『イミュータブル』と言います。」
生徒
「なんでそんなふうに変えられないものがあるんですか?」
先生
「変えられないことで、データの安全性が高まったり、Pythonの他の仕組みで使いやすくなったりします。例えば、frozensetは辞書のキーに使えますが、setは使えません。」
生徒
「そうなんですね!じゃあ、具体的にどう使うのか教えてください。」
先生
「はい、では基本の使い方から説明しますね!」
1. set(セット)とfrozenset(フローズンセット)の基本
まず、set(セット)は「同じ値を重複して持たない」データの集まりです。 リストと少し似ていますが、順番は保証されず、あとから自由に要素を追加・削除できるのが大きな特徴です。 このように中身を変更できるデータ型をミュータブルと呼びます。 初心者の方は「あとから中身を書き換えられる箱」だと思うとイメージしやすいでしょう。
fruits = {"りんご", "みかん", "バナナ"}
fruits.add("ぶどう") # 要素を追加
fruits.remove("みかん") # 要素を削除
print(fruits) # {'りんご', 'バナナ', 'ぶどう'}
一方、frozenset(フローズンセット)は、その名の通り「凍結されたセット」です。 作成した後は要素を追加したり削除したりできません。 このように中身を変更できないデータ型をイミュータブルと言います。 プログラムの途中で内容が変わらないことが保証されるため、安心して使える場面が多いのが特徴です。
frozen_fruits = frozenset(["りんご", "みかん", "バナナ"])
print(frozen_fruits) # frozenset({'りんご', 'みかん', 'バナナ'})
# frozen_fruits.add("ぶどう")
# 上の行を実行するとエラーになります(変更できないため)
つまり、「あとから変更したいならset」「作った内容を絶対に変えたくないならfrozenset」と覚えておくと、 Python初心者でも使い分けで迷いにくくなります。
2. ミュータブル(mutable)とイミュータブル(immutable)とは?
ミュータブルは「変えられる」という意味で、リストやsetがこれにあたります。
イミュータブルは「変えられない」で、タプルやfrozensetがこれです。
変更できないものは、プログラムの中で安心して使えたり、データの一貫性を保ちやすかったりします。
3. frozensetの使い道:辞書のキーや集合の要素に使える
Pythonの辞書(dictionary)はキーに使えるデータ型が限られていて、変更できるsetは使えません。でもfrozensetなら使えます。
my_dict = {}
key_set = frozenset(["りんご", "みかん"])
my_dict[key_set] = "果物セット"
print(my_dict) # {frozenset({'りんご', 'みかん'}): '果物セット'}
このようにfrozensetは、変更できないので辞書のキーや他のセットの要素にも使えます。
4. setとfrozensetの操作の違い
setは要素の追加・削除が自由にできますが、frozensetはできません。例えば以下のように操作を試みるとエラーになります。
s = {"a", "b", "c"}
fs = frozenset(["a", "b", "c"])
s.add("d") # OK
# fs.add("d") # エラー:frozensetは変更不可
s.remove("a") # OK
# fs.remove("a") # エラー:frozensetは変更不可
操作できるのは、setだけだと覚えておきましょう。
5. frozensetの作り方と基本メソッド
frozensetは、リストやセットなどから簡単に作れます。
fs1 = frozenset(["りんご", "みかん", "バナナ"])
fs2 = frozenset({"バナナ", "メロン"})
print(fs1)
print(fs2)
frozensetは読み取り専用の集合なので、集合演算(和、積、差など)ができます。
print(fs1 | fs2) # 和集合:{'りんご', 'みかん', 'バナナ', 'メロン'}
print(fs1 & fs2) # 積集合:{'バナナ'}
print(fs1 - fs2) # 差集合:{'りんご', 'みかん'}
6. まとめないけど覚えておきたいポイント
- setは変更可能(ミュータブル)で、要素の追加・削除ができる
- frozensetは変更不可(イミュータブル)で、一度作ったら要素を変えられない
- frozensetは辞書のキーやセットの要素に使えて便利
- 集合演算はsetもfrozensetも使える
- 用途に応じて使い分けることが重要
まとめ
今回の記事では、Pythonにおけるfrozenset(フローズンセット)の役割や、通常のset(セット)との決定的な違いについて詳しく解説してきました。プログラミング初心者の方にとって、「なぜわざわざ変更できないデータ型が存在するのか?」という疑問は当然のものです。しかし、今回学んだように、変更不可(イミュータブル)であるという性質は、Pythonのシステム内部でデータを「ハッシュ可能(Hashable)」として扱うために非常に重要な役割を果たしています。
frozensetとsetの最大の違いは「書き換え」の可否
最大の違いを一言で言えば、ミュータブル(変更可能)かイミュータブル(変更不可)かという点に尽きます。set型は、プログラムの実行中に動的に要素を増やしたり減らしたりする「バッファ」のような使い方が得意です。一方でfrozensetは、一度定義した「集合」を定数のように扱い、意図しない書き換えからデータを守るガードレールのような役割を担います。
実践的な活用シーン:辞書のキーとしての利用
実務的な開発においてfrozensetが最も輝くのは、「集合そのものを辞書のキーにしたい場合」です。通常のsetを辞書のキーに設定しようとすると、Pythonは「中身が変わる可能性があるものをキーにはできない」としてエラーを返します。以下のコードで、その動作の違いを確認してみましょう。
# 集合を辞書のキーにする試み
try:
bad_dict = {{1, 2}: "エラーになります"}
except TypeError as e:
print(f"setの場合: {e}")
# frozensetなら辞書のキーになれる
good_dict = {frozenset([1, 2]): "これは成功します"}
print(f"frozensetの場合: {good_dict}")
実行結果は以下のようになります。
setの場合: unhashable type: 'set'
frozensetの場合: {frozenset({1, 2}): 'これは成功します'}
集合演算の柔軟性
「変更できないなら計算も不便なのでは?」と思うかもしれませんが、そんなことはありません。積集合(&)や和集合(|)といった集合演算は、frozensetであっても自由に行えます。演算の結果として「新しいfrozensetオブジェクト」が生成されるため、元のデータが壊れる心配もありません。これにより、複雑なデータ分析やフィルタリング処理においても、安全性を確保しながら高度なロジックを組むことが可能になります。
開発における使い分けのコツ
基本的には、後から要素をいじる予定があるならsetを使い、一度決めたら変えたくない設定値のグループや、他のコレクションのキーとして活用したい場合はfrozensetを選ぶのがスマートなプログラミングと言えるでしょう。この使い分けができるようになると、Pythonコードの堅牢性が一段階アップします。
生徒
先生、まとめを読んでfrozensetの使い道がもっとはっきり分かりました!辞書のキーにできるっていうのが、一番の強みなんですね。
先生
その通りです!Pythonの世界では「変わらないこと」が「信頼」に繋がる場面が多いんですよ。辞書のキーや、他のセットの要素になるためには、その中身が勝手に変わらないという保証が必要なんです。
生徒
なるほど。「ハッシュ可能」っていう言葉も出てきましたが、これは「中身が変わらないから、Pythonがそのデータを一意に識別できる」という意味で合っていますか?
先生
素晴らしい理解です!例えば、出席名簿で名前がコロコロ変わる人がいたら管理できませんよね。frozensetは「名前が変わらない人」のように、システムが安心して管理できる存在なんです。逆にsetは、中身がどんどん変わる「掲示板」のようなイメージですね。
生徒
分かりやすいです。あと、集合演算についても気になったのですが、frozensetとsetを混ぜて計算することはできるんでしょうか?
先生
いい質問ですね!実は混ぜて計算することも可能です。例えば、こんなコードを見てみましょう。
regular_set = {"apple", "orange"}
frozen_set = frozenset(["orange", "banana"])
# 両方を合わせた和集合を作る
result = regular_set | frozen_set
print(type(result))
print(result)
<class 'set'>
{'orange', 'apple', 'banana'}
先生
この場合、左側に書いた型が優先されることが多いですが、計算自体はエラーなく行えます。ただし、結果がどちらの型になるかは演算の順番によって変わるので、基本的には同じ型で揃えておくのが無難ですよ。
生徒
演算の順番まで意識する必要があるんですね。奥が深いです!これからは、不用意に中身を変えたくないデータセットを作るときは、積極的にfrozensetを使ってみようと思います。
先生
ぜひそうしてください。コードを見た他の開発者にも「このデータは変更しちゃいけないんだな」という意図が伝わるので、可読性やメンテナンス性も向上しますよ。頑張ってくださいね!