I am writing a python script which calculates various quantities based on two parameters, the long radius and short radius of a spheroid. It occurred to me that I could write a spheroid class to do this. However, I'm new to object oriented design and wonder if you more experienced chaps can help me.
An instance is instantiated with parameters a and b for the long radius and short radius respectively, so I've designed the class as follows:
class Spheroid:
  def __init__(self,a,b):
    self.longax  = a
    self.shortax = b
One of the quantities I want to calculate is the volume. The volume of a spheroid is 4*pi/3 * a * b * b.
My question is, do I define a method or an attribute for this in my class?
e.g. I could define a method:
def Volume(self):
  return 4*pi/3 * self.longax * self.shortax * self.shortax
or I could just use an attribute:
self.volume = 4*pi/3 * self.longax * self.shortax * self.shortax
I could also just include it in the init method:
class Spheroid:
  def __init__(self,a,b):
    self.longax  = a
    self.shortax = b
    self.volume = 4*pi/3 * a * b * b.
Which is better to use and why? In general, when would I use a method and when would I use an attribute? I wouldn't normally care but I have a whole load of these to implement and I'd like to have an idea about OO design for future reference.
Thanks
EDIT:
After implementing properties as per Martijn's suggestion, I ended up with something like this:
class Spheroid(object):
  def __init__(self,a,b):
    self.shortax = a
    self.longax  = b
    self.alpha=self.longax/self.shortax
    @property
    def volume(self):
        return (4*np.pi/3) * self.shortax * self.shortax * self.longax
    @property
    def epsilon(self):
        return np.sqrt(1-self.alpha**(-2))
    @property
    def geometricaspect(self):
        return 0.5 + np.arcsin(self.epsilon)*0.5*self.alpha/self.epsilon
    @property
    def surfacearea(self):
        return 4*np.pi*self.shortax**2*self.geometricaspect
I instantiated an instance s = Spheroid() but whenever I try something like s.volume or s.epsilon I get an AttributeError:
AttributeError: 'Spheroid' object has no attribute 'volume'
What am I doing wrong here?
Also, in my init method I used self.alpha = self.longax/self.shortax instead of a/b, does this make any difference? Is one way preferable?
You have a 3rd option: make it both an attribute and a method, by using a property:
class Spheroid(object):
    def __init__(self, a, b):
        self.long  = a
        self.short = b
    @property
    def volume(self):
        return 4 * pi / 3 * self.long * self.short * self.short
You access .volume like an attribute:
>>> s = Spheroid(2, 3)
>>> s.volume
75.39822368615503
In order for the property descriptor to work correctly, in Python 2 you need to make sure your class inherits from object; in Python 3 the baseclass can be safely omitted.
In this case, the calculation of the volume is cheap enough, but a property lets you postpone having to calculate the volume until you actually need it.
The above example creates a read-only property; only a getter is defined:
>>> s.volume = None
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
You could easily cache the result of the property calculation:
class Spheroid(object):
    _volume = None
    def __init__(self, a, b):
        self.long  = a
        self.short = b
    @property
    def volume(self):
        if self._volume is None:
            self._volume = 4 * pi / 3 * self.long * self.short * self.short
        return self._volume
so that you only have to calculate it once per Spheroid instance.
What you use depends on many factors; how easy does your API need to be to use, how often will the volume be calculated, how many Spheroid instances will be created, etc. If you create a million of these in a loop but only ever need the volume for a handfull of them, it makes sense to use a property instead of setting the volume in the __init__.
If, however, your class could adjust itself based on the volume; say, by adjusting one of the radii automatically, then a @property makes more sense still:
class Spheroid(object):
    def __init__(self, a, b):
        self.long  = a
        self.short = b
    @property
    def volume(self):
        return 4 * pi / 3 * self.long * self.short * self.short
    @volume.setter
    def volume(self, newvolume):
        # adjust the short radius
        self.short = sqrt(newvolume / (4 * pi / 3 * self.long))
Now you have a spheroid which naturally adjusts it's short attribute as you adjust the volume:
>>> s = Spheroid(2, 1)
>>> s.volume
8.377580409572781
>>> s.volume = 75.39822368615503
>>> s.long, s.short
(2, 3.0)
Note: technically, anything you access on an object with .name notation is an attribute; methods included. For the purpose of this answer, I used your attribute as any value that is not called (doesn't use () after the name).
will you always use this data?
if not, you could use a property and then lazily compute it...
class Spheroid(object):
  def __init__(self,a,b):
    self.longax  = a
    self.shortax = b
    self._volume = None
  @property
  def volume(self):
      if self._volume is None :
           self._volume = 4*pi/3 * self.longax * self.shortax * self.shortax
      return self._volume
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With