readerboy7
readerboy7

Reputation: 133

What is a pythonic way to have property getters without setters with python dataclasses?

I have a situation where I need to store variables a,b, and c together in a dataclass, where c = f(a,b) and a,b can be mutated. I need c to be displayed along with a and b when printing the object, and c cannot be changed except through changing a or b. I felt the best way to do this was to have c be a value created with a property.

Minimal example of what I tried to use:

@dataclass
class Demo:
    a:int
    b:int
    c:int = field(init=False)

    @property
    def c(self) -> int:
        return self.a * self.b

However, calling this results in AttributeError: can't set attribute. I suspect the code is trying to set c to field(init=False), and failing because c doesn't have a setter.

Options I've considered (not counting alternatives I tried that makes the code crash in a different manner):

endnote:

    @c.setter
    def c(self, val):
        caller = sys._getframe().f_back.f_code.co_name
        if caller != '__init__':
            raise ValueError # or some other error

Upvotes: 3

Views: 4353

Answers (2)

Robᵩ
Robᵩ

Reputation: 168776

Your method c overwrites your field c -- the original field declaration is lost, including the init=False.

Try changing the order and using the default= parameter.

from dataclasses import dataclass, field


@dataclass
class Demo:
    @property
    def c(self) -> int:
        return self.a * self.b

    a: int
    b: int
    c: int = field(init=False, default=c)


d = Demo(5, 10)
print(d)
d.a = 10
print(d)
d.c = 4  # generates an AttributeError

Output from Python 3.8.0:

Demo(a=5, b=10, c=50)
Demo(a=10, b=10, c=100)
Traceback (most recent call last):
...
  File "/home/rob/src/plot/dc.py", line 20, in <module>
    d.c = 4  # generates an AttributeError
AttributeError: can't set attribute

Upvotes: 4

user2357112
user2357112

Reputation: 281683

If you want c to be a computed property, then take out the c field entirely and just leave the property:

@dataclass
class Demo:
    a:int
    b:int

    @property
    def c(self) -> int:
        return self.a * self.b

Fields are for stored data, which c should not be.

If you want c to show up in repr, you'll have to write the __repr__ method yourself. If later code needs c to show up in fields, then you probably need to rework your code at a deeper level, and possibly switch to something other than dataclasses.

Upvotes: 5

Related Questions