属性还是方法?

时间:2013-03-20 18:29:03

标签: python class methods attributes

我正在编写一个python脚本,它根据两个参数计算各种数量,即球体的长半径和短半径。在我看来,我可以写一个球体类来做到这一点。但是,我是面向对象设计的新手,并且想知道你是否有更多经验丰富的人可以帮助我。

实例用参数a和b分别实例化了长半径和短半径,所以我按如下方式设计了类:

class Spheroid:
  def __init__(self,a,b):
    self.longax  = a
    self.shortax = b

我想要计算的数量之一是音量。球状体的体积为4 * pi / 3 * a * b * b。

我的问题是,我是否在课堂上为此定义了方法或属性?

e.g。我可以定义一个方法:

def Volume(self):
  return 4*pi/3 * self.longax * self.shortax * self.shortax

或者我可以使用属性:

self.volume = 4*pi/3 * self.longax * self.shortax * self.shortax

我也可以将它包含在init方法中:

class Spheroid:
  def __init__(self,a,b):
    self.longax  = a
    self.shortax = b
    self.volume = 4*pi/3 * a * b * b.

哪个更好用,为什么?一般来说,我什么时候使用方法?何时使用属性?我通常不会关心,但我有很多这些要实现,我想了解OO设计以供将来参考。

由于

编辑:

根据Martijn的建议实施属性后,我最终得到了类似的结果:

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

我实例化了一个实例s = Spheroid(),但每当我尝试像s.volume或s.epsilon这样的东西时,我得到一个AttributeError:

AttributeError:'Spheroid'对象没有属性'volume'

我在这里做错了什么?

另外,在我的 init 方法中,我使用了self.alpha = self.longax / self.shortax而不是a / b,这有什么不同吗?有一种方式更好吗?

3 个答案:

答案 0 :(得分:9)

您有第3个选项:使用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

您可以像属性一样访问.volume

>>> s = Spheroid(2, 3)
>>> s.volume
75.39822368615503

为了使property描述符正常工作,在Python 2中,您需要确保您的类继承自object;在Python 3中,可以安全地省略基类。

在这种情况下,音量的计算足够便宜,但属性允许你推迟必须计算音量直到你真正需要它。

上面的示例创建了一个只读属性;只定义了一个getter:

>>> s.volume = None
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

您可以轻松缓存属性计算的结果:

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

这样您只需每{/ 1}个实例计算一次

您使用的内容取决于许多因素;您需要使用API​​的容易程度,计算量的频率,创建许多 Spheroid实例的方式等等。如果您在循环中创建了数百万个,但只需要对于其中一小部分的音量,使用属性而不是在Spheroid中设置音量是有意义的。

但是,如果您的课程可以根据音量调整;比方说,通过自动调整其中一个半径,__init__更有意义:

@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

    @volume.setter
    def volume(self, newvolume):
        # adjust the short radius
        self.short = sqrt(newvolume / (4 * pi / 3 * self.long))

注意:从技术上讲,您使用>>> s = Spheroid(2, 1) >>> s.volume 8.377580409572781 >>> s.volume = 75.39822368615503 >>> s.long, s.short (2, 3.0) 表示法访问对象的任何属性;方法包括在内出于本答案的目的,我使用您的.name作为未调用的任何值(在名称后不使用attribute)。

答案 1 :(得分:2)

你会一直使用这些数据吗?

如果没有,你可以使用一个属性,然后懒洋洋地计算它......

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

答案 2 :(得分:1)

由于以下原因,我将实现卷作为方法:

  1. 它可以从其他属性计算出来,因此可以节省计算空间(除非这是非常复杂的计算,而且你可以考虑缓存它)。
  2. 它不是对象的自然“特征”,例如对于圆,半径是属性而不是区域(这不是真正的格式定义)
  3. 像体积这样的东西是一种抽象的方法,如果你想拥有一系列对象并以多态方式计算每个对象的体积。