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