Search code examples
pythonobjectattributesimmutabilitypython-dataclasses

Initialize frozen dataclass attribute with function


I am working with frozen dataclasses in Python. My goal is to create a dataclass with its frozen attributes. However, one of the attributes is a function of the other ones. I want my class to be frozen (once it has been instantiated it can't be changed), but I can't find a way to specify the value of an attribute inside the class with a method.

As an example, let's suppose to have the following simple class:

@dataclasses.dataclass(frozen=True)
class Car:
    color: str
    size: int
    model: str
    price: float

The price of the car is function of its color, size and model. As an example:

def _compute_price(self, color, size, model):

    if color == 'red':
       color_surplus = 10
    else:
       color_surplus = 0

    if size > 10:
       size_surplus = 10
    else:
       size_surplus = 5

    if model == 'mustang':
       model_surplus = 100
    else:
       model_surplus = 0

    self.price = color_surplus + size_surplus + model_surplus

I would like to create my car like:

car = Car('red', 15, 'mustang') 

And automatically use the above sample function inside my class to initialize the missing price attribute. However, as soon as I specify self.price, Python tells me that it is a read only attribute and thus non-assignable. I also tried to use the __init__ function, but I still have the same problem:

@dataclasses.dataclass(frozen=True)
class Car:

    def __init__(self,  color: str, size: int, model: str, price: float):
        
        self.color = color #error here
        ...
        ...

    

Any solutions? My goal is to use a frozen dataclass while being able to specify an attribute with a private function (included in the class) of the other ones


Solution

  • Make price a property, wich are read only if you don't create a setter:

    @property
    def price(self):
    
        if self.color == 'red':
            color_surplus = 10
        else:
           color_surplus = 0
    
        if self.size > 10:
           size_surplus = 10
        else:
           size_surplus = 5
        if self.model == 'mustang':
           model_surplus = 100
        else:
           model_surplus = 0
    
        return color_surplus + size_surplus + model_surplus
    

    You can also use cached_property instead of property to not recompute the value each time you call it