'How to make non-frozen dataclass frozen, and vice versa?

I want to know any simple way to make the below dataclass bar frozen.

@dataclass
class Bar:
    foo: int
bar = Bar(foo=1)

In other words, I want the below function some_fn_to_freeze

frozen_bar = some_fn_to_freeze(bar)
frozen_bar.foo = 2 # Error

And, the inverse function some_fn_to_unfreeze

bar = som_fn_to_unfrozen(frozen_bar)
bar.foo = 3 # not Error


Solution 1:[1]

The standard way to mutate a frozen dataclass is to use dataclasses.replace:

old_bar = Bar(foo=123)
new_bar = dataclasses.replace(old_bar, foo=456)
assert new_bar.foo == 456

For more complex use-cases, you can use the dataclass utils module from: https://github.com/google/etils

It add a my_dataclass = my_dataclass.unfrozen() member, which allow to mutate frozen dataclasses directly

# pip install etils[edc]
from etils import edc

@edc.dataclass(allow_unfrozen=True)  # Add the `unfrozen()`/`frozen` method
@dataclasses.dataclass(frozen=True)
class A:
  x: Any = None
  y: Any = None


old_a = A(x=A(x=A()))

# After a is unfrozen, the updates on nested attributes will be propagated
# to the top-level parent.
a = old_a.unfrozen()
a.x.x.x = 123
a.x.y = 'abc'
a = a.frozen()  # `frozen()` recursively call `dataclasses.replace`

# Only the `unfrozen` object is mutated. Not the original one.
assert a == A(x=A(x=A(x = 123), y='abc'))
assert old_a == A(x=A(x=A()))

As seen in the example, you can return unfrozen/frozen copies of the dataclass, which was explicitly designed to mutate nested dataclasses.

@edc.dataclass also add a a.replace(**kwargs) method to the dataclass (alias of dataclasses.dataclass)

a = A()
a = a.replace(x=123, y=456)
assert a == A(x=123, y=456)

Solution 2:[2]

dataclass doesn't have built-in support for that. Frozen-ness is tracked on a class-wide basis, not per-instance, and there's no support for automatically generating frozen or unfrozen equivalents of dataclasses.

While you could try to do something to generate new dataclasses on the fly, it'd interact very poorly with isinstance, ==, and other things you'd want to work. It's probably safer to just write two dataclasses and converter methods:

@dataclass
class Bar:
    foo: int
    def as_frozen(self):
        return FrozenBar(self.foo)

@dataclass(frozen=True)
class FrozenBar:
    foo: int
    def as_unfrozen(self):
        return Bar(self.foo)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Conchylicultor
Solution 2 user2357112