Computer368106 views
高校倫理1440106 views
高校物理160203 views
数学講師2886227 views
小学算数1200427 views
教育149510 views
高校日本史190571 views
いろは3010814 views
中学英語811710 views
高校化学2924392 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

frozen=True でイミュータブルなデータクラスを作る|Python

Python の dataclass はデフォルトでミュータブルだ。インスタンス生成後にフィールドを自由に書き換えられる。しかし、設定値や座標のように「一度作ったら変更されるべきでない」データも多い。そうしたケースで使うのが frozen=True オプションである。

frozen=True の基本

@dataclass(frozen=True) を指定すると、インスタンスのフィールドへの代入が禁止される。値を変更しようとすると FrozenInstanceError が発生する。

from dataclasses import dataclass

@dataclass(frozen=True)
class Point:
    x: float
    y: float

p = Point(1.0, 2.0)
print(p.x)  # 1.0

p.x = 3.0  # FrozenInstanceError

内部的には __setattr____delattr__ がオーバーライドされ、属性の変更・削除を一律にブロックする仕組みになっている。通常の dataclass が生成する __setattr__ をそのまま使うのではなく、例外を送出するバージョンに差し替えているわけだ。

通常の dataclass との比較

frozen を指定しない場合と比べると、振る舞いが明確に変わる。

通常の dataclass

フィールドへの再代入が自由にできる。辞書のキーや集合の要素には使えない。__hash__ はデフォルトで None になる。

frozen=True

フィールドへの再代入で FrozenInstanceError が発生する。__hash__ が自動生成されるため、辞書のキーや集合の要素として使える。

この違いは設計上の判断に直結する。データが変更されないことをコードレベルで保証できるのは、バグの予防において大きな意味を持つ。

ハッシュ可能になる利点

frozen なデータクラスは __hash__ が自動生成される。これにより、dict のキーや set の要素として使えるようになる。

from dataclasses import dataclass

@dataclass(frozen=True)
class Color:
    r: int
    g: int
    b: int

palette = {
    Color(255, 0, 0): "red",
    Color(0, 128, 0): "green",
    Color(0, 0, 255): "blue",
}

print(palette[Color(255, 0, 0)])  # red

unique_colors = {Color(255, 0, 0), Color(0, 0, 255), Color(255, 0, 0)}
print(len(unique_colors))  # 2

通常の dataclass でこれをやると TypeError: unhashable type になる。ハッシュ可能であることは、キャッシュやルックアップテーブルを構築するときにも重要だ。

値を変更したい場合は replace を使う

frozen なインスタンスは変更できないが、一部のフィールドだけ変えた新しいインスタンスを作ることはできる。dataclasses.replace を使えばよい。

from dataclasses import dataclass, replace

@dataclass(frozen=True)
class Config:
    host: str
    port: int
    debug: bool

base = Config(host="localhost", port=8080, debug=False)
dev = replace(base, debug=True)

print(base)  # Config(host='localhost', port=8080, debug=False)
print(dev)   # Config(host='localhost', port=8080, debug=True)
print(base is dev)  # False

元のインスタンスはそのまま残り、変更箇所だけ反映された別のオブジェクトが生成される。関数型プログラミングでいう「永続データ構造」に近い考え方で、状態の変遷を追跡しやすくなる。

post_init での初期化

frozen なデータクラスでは __post_init__ 内であっても通常の代入はできない。object.__setattr__ を使って直接書き込む必要がある。

from dataclasses import dataclass, field

@dataclass(frozen=True)
class Circle:
    radius: float
    area: float = field(init=False)

    def __post_init__(self):
        object.__setattr__(self, "area", 3.14159 * self.radius ** 2)

c = Circle(radius=5.0)
print(c.area)  # 78.53975

self.area = ... と書くと FrozenInstanceError になるため、object.__setattr__ で Python のオブジェクトシステムを直接叩く。これは frozen dataclass における定番のパターンである。

この object.<span class="rollpie-article-note__mark">setattr</span> による書き込みは <span class="rollpie-article-note__mark">post_init</span> 内でのみ使うべきで、外部から呼び出すのは frozen の意図に反する。あくまで初期化時の計算フィールド設定に限定するのが望ましい。

生成後に外部から object.setattr を使えば変更は可能だが、それは設計上の契約違反になる。

frozen と継承

frozen な dataclass を継承する場合、子クラスも frozen でなければならない。frozen と非 frozen の混在は許可されていない。

from dataclasses import dataclass

@dataclass(frozen=True)
class Base:
    x: int

@dataclass(frozen=True)
class Child(Base):
    y: int

c = Child(x=1, y=2)
print(c)  # Child(x=1, y=2)

もし子クラスで frozen=True を外すと TypeError が発生する。

from dataclasses import dataclass

@dataclass(frozen=True)
class Base:
    x: int

@dataclass  # frozen=True がない
class Child(Base):
    y: int

# TypeError: cannot inherit frozen dataclass from a non-frozen one

frozen=True の dataclass を継承するとき、子クラスはどうするべきか?

  • frozen を指定しなくてよい
  • 子クラスにも frozen=True を指定する
  • eq=False を指定する
  • __hash__ を手動で定義する
__RESULT__

frozen な親クラスを継承する場合、子クラスにも frozen=True が必要です。frozen と非 frozen の混在は TypeError になります。

frozen を使うべき場面

frozen dataclass が特に効果を発揮するのは、次のような場面だ。

設定値や定数の表現

アプリケーション設定、接続情報、環境変数をまとめたオブジェクトなど。一度読み込んだら変更されるべきでないデータに適している。

辞書のキーや集合の要素

ハッシュ可能であることが求められる場面。複合キー(複数のフィールドを組み合わせた一意識別子)としても使える。

マルチスレッド環境

イミュータブルなオブジェクトはスレッド間で安全に共有できる。ロックなしで読み取りが可能になるため、並行処理のコードが簡潔になる。

一方で、頻繁にフィールドを更新するようなデータには向かない。毎回 replace で新しいインスタンスを作るのはオーバーヘッドになるため、そうしたケースでは通常の dataclass を使うほうが合理的である。設計段階で「このデータは変更されるか」を判断し、適切に使い分けることが重要だ。