How to freeze individual field of non-frozen dataclass?
Asked Answered
D

3

10

Is it possible to specify that a single field is frozen in a non-frozen dataclass? Something like this:

@dataclass
class Data:
    fixed: int = field(frozen=True)
    mutable: int = field(frozen=False)

d = Data(2, 3)
d.mutable = 5   # fine
d.fixed = 7     # raises exception

I realize this can be done manually with properties and setters accessing a private data field, but then we lose some of the advantages of dataclasses: for one, the private data field needs a different name, which means the auto-generated constructor annoyingly has different parameter names than the fields.

Dionne answered 9/9, 2020 at 2:7 Comment(1)
dataclasses does not support this, but attrs (the project from which dataclasses was lifted) does: see attrs.org/en/stable/api.html#attr.setters.NO_OPLymphangitis
P
9

I came here looking for the same but, like @wim said in comments, dataclass does not support this, but using attrs it is possible:

from attrs import define, field
from attrs.setters import frozen

@define
class Data:
    fixed: int = field(on_setattr=frozen)
    mutable: int

d = Data(2, 3)
d.mutable = 5   # fine
d.fixed = 7     # raises exception
Pettus answered 28/3, 2022 at 11:18 Comment(0)
D
0
import inspect
from dataclasses import dataclass, fields, field

@dataclass
class NamedDataclass:
    name: str = field(init=True)

    def __setattr__(self, key, value):
        stack = inspect.stack()
        if len(stack) < 2 or stack[1].function != '__init__':
            assert key != 'name'
        super().__setattr__(key, value)
Diverse answered 17/1, 2024 at 14:2 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Swaggering
M
0
def frozen_field_support(cls):
    """
    Class decorator support dataclass field level freezing
    Support for idempotency i.e. if value is unchanged, no exception is raised

    Example:

    @frozen_field_support
    @dataclass
    class X:
        name: str = field(metadata={"frozen": True})

    x = X(name="whatever")
    x.name = "somethingelse" # FrozenField exception raised

    @raises FrozenField
    """
    def _setattr_(this, name, value):
        _field = cls.__dataclass_fields__.get(name, {})
        _meta = getattr(_field, "metadata", {})
        is_frozen = _meta.get("frozen", False)
        if not is_frozen:
            return super(cls, this).__setattr__(name, value)

        try:
            current_value = getattr(this, name)
            if value != current_value:
                raise FrozenField(f"Field '{name}' already has value= {current_value}")
        except AttributeError:  # NOQA
            # dataclass not initialized yet...
            pass

        super(cls, this).__setattr__(name, value)

    setattr(cls, "__setattr__", _setattr_)
    return cls
Munificent answered 4/8, 2024 at 13:47 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.