Sam Brock
Sam Brock

Reputation: 73

Using the @property decorator for interrelated attributes within a Python class

This question relates to using the @property decorator within a class in Python. I want to create a class with two public attributes (say a and b). I also want the class to have a further public attribute (say c) that is always equal to some calculation based on the other attributes within the class (say a + b = c). The attribute c needs to be publicly getable, but because of the dependency on a and b, I don't really want it to be publicly getable if that's possible.

The code block below won't run because when self.a is set in line 3, a.setter is being called which raises an AttributeError because self.b in line 14 is not yet set.

Is using the @property decorator the correct thing to do in this circumstance?

What is the most Pythonic way to correctly set the attributes a, b and c during the initialisation method? Ideally without having to define the relationship between a, b and c in more than one place if this is possible (i.e. unlike in the code below where the relationship between a, b and c is defined on lines 13 and 22)?

Also, is it possible to enforce that the user is not able to set the value of c directly and what is the most Pythonic way of doing this?

class ExampleClass1:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    @property
    def a(self):
        return self._a

    @a.setter
    def a(self, a):
        self._a = a
        self.c = self._a + self.b

    @property
    def b(self):
        return self._b

    @b.setter
    def b(self, b):
        self._b = b
        self.c = self._b + self.a

Upvotes: 1

Views: 166

Answers (2)

pumpkinpieter
pumpkinpieter

Reputation: 1

Just came on your post.

I was having the exact same problem and I think that this seems to work:

class ExampleClass1:

    def __init__(self, a, b):
        self.a = a
        self.b = b
        
    @property
    def a(self):
        return self._a
    
    @a.setter
    def a(self, value):
        self._a = value
        try:
            self.c = self._a + self.b
        except AttributeError:
            self.c = self._a
    
    @property
    def b(self):
        return self._b
    
    @b.setter
    def b(self, value):
        self._b = value
        try:
            self.c = self.a + self._b
        except AttributeError:
            self.c = self._b

When I've tried to use the property decorator a single time for 'c', it results in the sum of 'a' and 'b' being performed every time you access the attribute 'c'.

To avoid this, we need to use the setter method as you suggested, but of course we run into that loop. The try-except seems to beat this.

Upvotes: 0

Daniel Roseman
Daniel Roseman

Reputation: 599470

I think you're approaching this the wrong way. If you need an attribute c that is always the sum of a and b, then it's that attribute that needs to be a property:

@property
def c(self):
    return self.a + self.b

and you don't need properties for a or b at all.

This also solves the problem of making it non-settable; if you don't define a setter for c, it can't be set directly.

Upvotes: 1

Related Questions