In Python, "frozen" means an object cannot be modified. For example, consider set and frozenset:
>>> s = set((1, 2, 3))
>>> s
{1, 2, 3}
>>> s.add(4)
>>> s
{1, 2, 3, 4}
>>> fs = frozenset((1, 2, 3))
>>> fs
frozenset({1, 2, 3})
>>> fs.add(4)
...
AttributeError: 'frozenset' object has no attribute 'add'
Likewise, creating a dataclass with frozen=True means its instances are frozen and cannot be changed.
Be aware that frozen only applies to the dataclass instance itself – a frozen dataclass can contain mutable items such as lists, and a regular dataclass can contain frozen/immutable items such as tuples.
The point of frozen objects is to avoid accidental modification, and to guarantee a consistent value.
- The former is advantageous to avoid bugs. When an object is not intended to be modified, making it
frozenreveals accidental modification via an immediate error. - The latter allows use as immutable object, for example the keys of a
dict. Afrozendataclass is by default hashable and suitable as adictkey.
from dataclasses import dataclass
@dataclass(frozen=True)
class Frozen:
x: int
y: int
named_points = {Frozen(0, 0): "Origin"}
Note that hashability does not just depend on the dataclass but is recursive – a frozen dataclass containing a list is not hashable, because the list is not hashable.
Well, you can do it by directly modifying the __dict__ member of the instance modifying the attribute using object.__setattr__(...)1, but why??? Asking specifically for immutable and then making it mutable is... indecisive. But if you must:
from dataclasses import dataclass
@dataclass(frozen=True)
class Foo:
id: str
name: str
def strip_id(self):
object.__setattr__(self, 'id', None)
foo=Foo(10, 'bar')
>>> foo
Foo(id=10, name='bar')
>>> foo.strip_id()
>>> foo
Foo(id=None, name='bar')
Any way of doing this is probably going to seem hacky... because it requires doing things that are fundamentally the opposite of the design.
If you're using this as a signal to other programmers that they should not modify the values, the way that is normally done in Python is by prefixing the variable name with a single underscore. If you want to do that, while also making the values accessible, Python has a builtin module called property, where (from the documentation) "typical use is to define a managed attribute":
from dataclasses import dataclass
@dataclass
class Foo:
_name: str
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
@name.deleter
def name(self):
self._name = None
Then you can use it like this:
>>> f=Foo()
>>> f.name = "bar"
>>> f.name
'bar'
>>> f._name
'bar'
>>> del f.name
>>> f.name
>>> f._name
The decorated methods hide the actual value of _name behind name to control how the user interacts with that value. You can use this to apply transformation rules or validation checks to data before it is stored or returned.
This doesn't quite accomplish the same thing as using @dataclass(frozen=True), and if you try declaring it as frozen, you'll get an error. Mixing frozen dataclasses with the property decorator is not straightforward and I have not seen a satisfying solution that is concise and intuitive. @Arne posted this answer, and I found this thread on GitHub, but neither approach is very inspiring; if I came across such things in code that I had to maintain, I would not be very happy (but I would be confused, and probably pretty irritated).
1: Modified as per the answer by @Arne, who observed that the internal use of a dictionary as the data container is not guaranteed.
As a slight improvement over Z4-tier's solution, please use object.__setattr__ instead of self.__dict__ to manipulate attributes of a frozen dataclass. The fact that classes use a dictionary to store their attributes is just the default behavior, and dataclasses in particular will regularly use __slots__ instead because it reduces the memory footprint.
from dataclasses import dataclass
@dataclass(frozen=True)
class Foo:
id: str
name: str
def strip_id(self):
object.__setattr__(self, 'a', None)
Static-only `frozen` data classes (or other ways to avoid runtime overhead) - Typing - Discussions on Python.org
Idiomatic way to create a frozen + mutable dataclass pair?
frozen dataclass inheritance is not strictly checked in multiple inheritance
Dataclasses freezing specific fields should be possible - Ideas - Discussions on Python.org
Videos
I have written another article on Python, this time on the frozen dataclasses.
I have explained how to write simple frozen dataclasses, and why you need them.
Please, read here
This is a "friend link" that is not behind the paywall.
Wrote a quick TIL on how to statically enforce frozen data classes in Python. Had to resort to crowd sourcing & good ol' stackoverflow to figure this one out since LLMs were of no help:
https://rednafi.com/python/statically_enforcing_frozen_dataclasses/