LaTeX962153 views
中学理科1630641 views
りんご209490 views
数学講師2886227 views
中学数学623755 views
高校日本史190571 views
雑学1473612 views
いろは3010814 views
世界の国564520 views
英語613639 views
Help
Tools
NewsSpreadsheetCalendarSlidesTier ListPen ToolIllustrationCrayonPixel ArtASCII ArtPerspectiveEndless StairsGraphMind MapER DiagramFamily TreeMemeCurved TextImage EditorMosaicRetro FilterPencil SketchOCR/HighlighterMakeup EditorFaviconVideo TrimmerScrolling VideoVideo TitleColor PickerColor ExtractorBonfireFireworksWater RippleWater SplashBreaking GlassWood GrainMarble TextureCSS ButtonIcon MakerBar ChartGrouped Bar ChartStacked Bar ChartPie ChartLine ChartArea ChartStacked Area ChartScatter Plot3D Bar Chart3D Pie ChartBar Chart RaceBubble ChartPopulation PyramidPictogramEarningsCandlestick ChartInvestment RiskMortgage SimulatorCalculatorMatrix CalculatorFunction GraphPolynomial ExpansionVenn DiagramField VisualizerRubik's Cube Group TheoryTraveling SalesmanVoronoi and DelaunayFractalColumn ArithmeticDraw Math FiguresArithmetic AnimationArithmetic Word ProblemsCounting with Tree DiagramsCube NetsCross SectionsMotion PathMechanicsWavesLight and LensesThermodynamicsHow Semiconductors WorkMolecular StructuresAtomic OrbitalsElectrochemical CellsChemical EquilibriumCrystal LatticesBuffer pHComplex IonsDNA Double HelixCell DivisionMembrane ChannelsNerve ImpulseRock ClassificationWeatherConstellationsSolar and Lunar Eclipses3D ModelingFloor PlanSeismic StructuresIntersection TurnMaglevCooking AnimationOrigamiLive Viewer CountGeoJSON MapRailway MapPopulation MapCrime MapLand Price MapSchool MapShrine and Castle MapHouse of Representatives MapWord MapSolitaireReversiHakoiri MusumeChessHamburgerRippleSlide Puzzle MakerNeon PinballNovel MakerJapanese Typing PracticePiano Score EditorMusic Theory

English

Python の set vs frozenset(使い分けの基準)

set と frozenset はどちらも集合を表すが、可変性(mutability)に違いがある。この違いが使い分けの基準になる。

可変性の違い

set は変更可能で、frozenset は変更不可能だ。

# set は変更できる
s = {1, 2, 3}
s.add(4)
s.remove(1)
print(s)  # {2, 3, 4}

# frozenset は変更できない
fs = frozenset([1, 2, 3])
# fs.add(4)     # AttributeError
# fs.remove(1)  # AttributeError

frozenset には add(), remove(), update() などの変更メソッドが存在しない。

set

add, remove, update, clear など変更メソッドあり

frozenset

変更メソッドなし。作成後は要素を変えられない

ハッシュ可能性

frozenset はハッシュ可能なので、辞書のキーや別の集合の要素にできる。

# frozenset を辞書のキーに
permissions = {
    frozenset(["read"]): "viewer",
    frozenset(["read", "write"]): "editor",
    frozenset(["read", "write", "admin"]): "admin"
}

user_perms = frozenset(["read", "write"])
print(permissions[user_perms])  # editor

# set はキーにできない
# {{"read", "write"}: "editor"}  # TypeError

「集合の集合」も frozenset なら作れる。

# 冪集合(すべての部分集合の集合)
power_set = {
    frozenset(),
    frozenset([1]),
    frozenset([2]),
    frozenset([1, 2])
}
print(power_set)

set はハッシュ不可能なので、これはできない。

集合演算の結果

両者とも集合演算ができるが、結果の型は左オペランドに依存する。

s = {1, 2, 3}
fs = frozenset([3, 4, 5])

# set が左だと set が返る
print(type(s | fs))   # <class 'set'>
print(type(s & fs))   # <class 'set'>

# frozenset が左だと frozenset が返る
print(type(fs | s))   # <class 'frozenset'>
print(type(fs & s))   # <class 'frozenset'>

メソッドの場合も同様に、呼び出し元の型が維持される。

s = {1, 2, 3}
fs = frozenset([3, 4, 5])

print(type(s.union(fs)))   # <class 'set'>
print(type(fs.union(s)))   # <class 'frozenset'>

変更が必要な場合の対処

frozenset の内容を変更したい場合は、一度 set に変換する。

fs = frozenset([1, 2, 3])

# 変更したい場合
temp = set(fs)
temp.add(4)
temp.remove(1)

# 再び frozenset に
fs = frozenset(temp)
print(fs)  # frozenset({2, 3, 4})

毎回変換するのはコストがかかるため、頻繁に変更するなら最初から set を使うべきだ。

使い分けの基準

set を選ぶ場面

要素の追加・削除が必要。動的に内容が変わる。一時的な作業用の集合。

frozenset を選ぶ場面

辞書のキーにしたい。集合の集合を作りたい。不変性を保証したい。関数の引数のデフォルト値にしたい。

デフォルト引数としての利用

ミュータブルなデフォルト引数は Python のよくある落とし穴だ。frozenset ならこの問題を回避できる。

# 危険な例
def bad_func(exclude=set()):
    exclude.add("default")
    return exclude

print(bad_func())  # {'default'}
print(bad_func())  # {'default', 'default'}(意図しない動作)

# 安全な例
def good_func(exclude=frozenset()):
    working = set(exclude)
    working.add("default")
    return working

print(good_func())  # {'default'}
print(good_func())  # {'default'}(期待どおり)

frozenset はイミュータブルなので、デフォルト値として安全に使える。

キャッシュとの相性

functools.lru_cache はハッシュ可能な引数しか受け付けない。set を引数にしたい場合は frozenset に変換する。

from functools import lru_cache

@lru_cache(maxsize=100)
def expensive_calculation(items: frozenset):
    return sum(items) ** 2

# frozenset を渡す
result = expensive_calculation(frozenset([1, 2, 3]))
print(result)  # 36

# set は直接渡せない
# expensive_calculation({1, 2, 3})  # TypeError

呼び出し側で変換するか、ラッパー関数を作るとよい。

def calculate(items):
    return expensive_calculation(frozenset(items))

print(calculate([1, 2, 3]))  # 36
print(calculate({1, 2, 3}))  # 36

パフォーマンスの違い

メモリ使用量と検索速度は、set と frozenset でほぼ同等だ。

import sys

s = set(range(1000))
fs = frozenset(range(1000))

print(sys.getsizeof(s))   # 32984
print(sys.getsizeof(fs))  # 32984

イミュータブルだからといって追加のオーバーヘッドはない。

意図の明確化

frozenset を使うことで、そのデータが変更されないという意図を明示できる。

# 設定値として不変であることを明示
ALLOWED_METHODS = frozenset(["GET", "POST", "PUT", "DELETE"])

def is_allowed(method):
    return method in ALLOWED_METHODS

コードを読む人に「この集合は変更されない」というメッセージを伝えられる。これは型アノテーションでも表現できるが、frozenset を使えば実行時にも変更を防止できる。

型アノテーションでの使い分け

typing モジュールでは、それぞれの型を明示できる。

from typing import Set, FrozenSet

def process_tags(tags: Set[str]) -> FrozenSet[str]:
    # 処理後は不変の集合として返す
    return frozenset(tag.lower() for tag in tags)

引数として受け取るときは Set(変更の可能性あり)、戻り値として返すときは FrozenSet(変更不可を保証)という使い分けも一つのパターンだ。