1

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?

5

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...

  • Hi Idan - yes, this is a simplified case. I'm actually modelling a file allocation on an IBM mainframe. The file has a 'block size' (blksize) attribute which determines how much space the file actually occupies on disk. Changing the blksize changes how many tracks of disk space the file uses. So the file will have blksize and numTracks attributes from which you can calculate the number of bytes in the file. However, you might wish to reallocate the file with a different blksize and want to know how many tracks it'll take up with the new blksize. – Steve Ives May 11 '15 at 19:57
  • Sounds like you'll want to keep numBytes(or however you want to call it) - since that's the actual size of the file visible to the users - and blksize, since that's a configuration, and calculate numTracks from them whenever it is queried. If you want a setter for numTracks, it should only modify blksize since it makes more sense to do that than to trim the actual data... – Idan Arye May 11 '15 at 22:40
  • 1
    Idan - a better analogy might have been a rectangle, with width and height and area. Change one whilst keeping another constant and you'll have to recalculate things. I guess I'm really asking if using setters and getters is the accepted Python way of doing things? I.e. In the setter for one property, recalculate any dependant properties? I guess I'd have to be careful not to get into a lops, if the setter for A recalculated B and the setter for B recalculated A? – Steve Ives May 12 '15 at 8:47
  • This is a question of design. Getters are easy, but you need to decide which attributes should have setters and what these setters should do. In the case of the rectangle, for example, you need to decide if the area setter should change the width only, the height only, both while keeping proportion or whatever formula you want - and it's also acceptable to say it doesn't have a setter. But keeping the 3 attributes attributes as actual member fields is not a good idea. Encapsulation allows you to keep consistency, but why bother when you can avoid the possibility of inconsistencies altogether? – Idan Arye May 12 '15 at 9:44
  • 1
    A member field is property that's actually stored in memory as part of the object(instead of being calculated on the spot). I don't know how "Pythonic" calculated properties are, but they are part of Python, with the built-in @property decorator, and I see no reason not to use them. – Idan Arye May 12 '15 at 14:18
0

A fool-proof and robust way of dealing with dependent attributes is to use my small pure-python module exactly for handling acyclic dependencies in python class attributes, also known as dataflow programming.

Sometimes it is not feasible to simply update all attributes when one is changed. Ideally, changing an attribute doesn't cause any updates. Only getting an attribute should cause updates, and only the required updates.

That is exactly what the module does. It is very easy to implement in any class definition.

For instance, consider an attribute dependency structure shown below:

enter image description here

This dependency structure is completely defined using the library's descriptors:

 class DataflowSuccess():

    # The following defines the directed acyclic computation graph for these attributes.
    a1 = IndependentAttr(init_value = 1, name = 'a1')
    a2 = DeterminantAttr(dependencies = ['a1'], calc_func = 'update_a2', name = 'a2')
    a3 = DeterminantAttr(dependencies = ['a2'], calc_func = 'update_a3', name = 'a3')
    a4 = DeterminantAttr(dependencies = ['a1','a2'], calc_func = 'update_a4', name = 'a4')
    a5 = DeterminantAttr(dependencies = ['a1','a2','a3','a6'], calc_func = 'update_a5', name = 'a5')
    a6 = IndependentAttr(init_value = 6, name = 'a6')
    a7 = DeterminantAttr(dependencies = ['a4','a5'], calc_func = 'update_a7', name = 'a7')


    # ...... define the update functions update_a2, update_a3 etc
    def update_a2():
        ....

The module takes care of the rest. Changing an attribute causes its children (attributes it affects) (and their children, etc) to know their values need to be recalculated in the future. When the value of an attribute is requested by calling __get__ (for instance by executing obj.a5), only the required updates occur, all automatically behind the scenes.

If it doesn't suit your needs, at least it is a helpful example of how the descriptor functionalities work. This example is available at the github page. Descriptors provide lots of flexibility for modifying how __get__ and __set__ and __del__ work for your class attributes, which can solve dependency problems.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.