Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Attribute or Method?

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?

like image 497
user1654183 Avatar asked Mar 20 '13 18:03

user1654183


2 Answers

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

like image 157
Martijn Pieters Avatar answered Sep 28 '22 08:09

Martijn Pieters


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
like image 24
Jonathan Vanasco Avatar answered Sep 28 '22 08:09

Jonathan Vanasco