Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make attribute in dataclass read-only?

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?

like image 545
Cleb Avatar asked Apr 15 '20 19:04

Cleb


People also ask

How do I make an attribute read-only?

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.

What is read-only in Python?

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.

What is data class Python?

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.

What is a 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.


4 Answers

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.

like image 84
Michal Vašut Avatar answered Sep 25 '22 03:09

Michal Vašut


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')
like image 44
Melendowski Avatar answered Sep 22 '22 03:09

Melendowski


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
like image 38
juanpa.arrivillaga Avatar answered Sep 24 '22 03:09

juanpa.arrivillaga


import operator

@dataclass
class Enum:
    name: str = property(operator.attrgetter("_name")) 

    def __init__(self, name):
        self._name = name
like image 29
shaurun Avatar answered Sep 22 '22 03:09

shaurun