Python – best way to have interdependent variable in a class

propertiespythonsetters

Newbie Python questions: I want to model something that can have a several inter-connected instance variables and if I set one, I want to recalculate the others. Any can be set.

E.g. a circle – each instance has an area, circumference and radius (let's ignore the diameter). If I set the area, I want the circumference & radius re-calculated and vice-versa. Should I :

class circle:

   def area(self):
       return cmath.pi*self.radius*self.radius

   def circumference(self):
      return 2 * cmath.pi * self.radius

   def radius(self):
      return self.circumference/(2*cmath.pi)

circle1 = circle()
circle1.radius = 5
print "Circle1's area is {0} and its circumference is {1}".format(circle1.area, circle1.circumference) 

Now there's nothing in the code sample to calculate the area once the radius has been set, so do I need @property with setters and getters for the radius, area and circumference to calculate the other attributes when this one is set or am I barking up completely the wrong tree?

Best Answer

Instead of keeping 3 member fields and synchronizing them, you only need to store one actual value in memory - the radius is a good choice in this example - and always use it:

import cmath

class Circle(object):
    def __init__(self, radius=0.0):
        self._radius = radius

    @property
    def radius(self):
       return self._radius

    @radius.setter
    def radius(self, radius):
        self._radius = float(radius)

    @property
    def area(self):
       return cmath.pi * self._radius * self._radius

    @area.setter
    def area(self, area):
        self._radius = (area / cmath.pi) ** 0.5

    @property
    def circumference(self):
        return 2 * cmath.pi * self._radius

    @circumference.setter
    def circumference(self, circumference):
        self._radius = circumference / cmath.pi / 2

circle1 = Circle()
circle1.radius = 5
print "Circle1's radius is {0}, it's area is {1} and it's circumference is {2}".format(circle1.radius, circle1.area, circle1.circumference)

circle1.area = 5
print "Circle1's radius is {0}, it's area is {1} and it's circumference is {2}".format(circle1.radius, circle1.area, circle1.circumference)

circle1.circumference = 5
print "Circle1's radius is {0}, it's area is {1} and it's circumference is {2}".format(circle1.radius, circle1.area, circle1.circumference)

This solution seems too obvious though - is there a specific reason you want to have separate member fields? You can usually find a smaller number of values you can calculate the rest from...

Related Topic