Let's say I have a class like this:
class C:
def __init__(self, stuff: int):
self._stuff = stuff
@property
def stuff(self) -> int:
return self._stuff
then stuff
is read-only:
c = C(stuff=10)
print(c.stuff) # prints 10
and
c.stuff = 2
fails as expected
AttributeError: can't set attribute
How can I get the identical behavior using a dataclass? If I wanted to also have a setter
, I could do:
@dataclass
class DC:
stuff: int
_stuff: int = field(init=False, repr=False)
@property
def stuff(self) -> int:
return self._stuff
@stuff.setter
def stuff(self, stuff: int):
self._stuff = stuff
But how could I do it without the @stuff.setter
part?
If you need to make a read-only attribute in Python, you can turn your attribute into a property that delegates to an attribute with almost the same name, but with an underscore prefixed before the its name to note that it's private convention.
Introduction to the Python readonly propertyTo define a readonly property, you need to create a property with only the getter. However, it is not truly read-only because you can always access the underlying attribute and change it. The read-only properties are useful in some cases such as for computed properties.
Data classes are one of the new features of Python 3.7. With data classes, you do not have to write boilerplate code to get proper initialization, representation, and comparisons for your objects. You have seen how to define your own data classes, as well as: How to add default values to the fields in your data class.
A data class is a list of data set allocation attributes and their values. You cannot assign a data class to an object; however, data class may be used for allocation of a scratch tape to be used to write objects.
from dataclasses import dataclass
@dataclass(frozen=True)
class YourClass:
"""class definition"""
https://docs.python.org/3/library/dataclasses.html#frozen-instances
After instantiation of the class, when trying to change any of its properties, the exception is raised.
To get the boilerplate reduction that dataclass
provides I found the only way to do this is with a descriptor.
In [236]: from dataclasses import dataclass, field
In [237]: class SetOnce:
...: def __init__(self):
...: self.block_set = False
...: def __set_name__(self, owner, attr):
...: self.owner = owner.__name__
...: self.attr = attr
...: def __get__(self, instance, owner):
...: return getattr(instance, f"_{self.attr}")
...: def __set__(self, instance, value):
...: if not self.block_set:
...: self.block_set = True
...: setattr(instance, f"_{self.attr}", value)
...: else:
...: raise AttributeError(f"{self.owner}.{self.attr} cannot be set.")
In [239]: @dataclass
...: class Foo:
...: bar:str = field(default=SetOnce())
In [240]: test = Foo("bar")
In [241]: test.bar
Out[241]: 'bar'
In [242]: test.bar = 1
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-242-9cc7975cd08b> in <module>
----> 1 test.bar = 1
<ipython-input-237-bddce9441c9a> in __set__(self, instance, value)
12 self.value = value
13 else:
---> 14 raise AttributeError(f"{self.owner}.{self.attr} cannot be set.")
15
AttributeError: Foo.bar cannot be set.
In [243]: test
Out[247]: Foo(bar='bar')
Because using the decorator in the class definition essentially triggers the @dataclass
decorator to use the property object as a default field, it doesn't play nice. You can set the property outside like:
>>> from dataclasses import dataclass, field
>>> @dataclass
... class DC:
... _stuff: int = field(repr=False)
... stuff: int = field(init=False)
...
>>> DC.stuff = property(lambda self: self._stuff) # dataclass decorator cant see this
>>> dc = DC(42)
>>> dc
DC(stuff=42)
>>> dc.stuff
42
>>> dc.stuff = 99
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
import operator
@dataclass
class Enum:
name: str = property(operator.attrgetter("_name"))
def __init__(self, name):
self._name = name
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With