6

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

一个实例分别用参数 a 和 b 为长半径和短半径实例化,所以我设计的类如下:

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

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

我的问题是,我是否在我的类中为此定义了方法或属性?

例如我可以定义一个方法:

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:“球体”对象没有属性“体积”

我在这里做错了什么?

另外,在我的初始化方法中,我使用了 self.alpha = self.longax/self.shortax 而不是 a/b,这有什么区别吗?一种方式更可取吗?

4

3 回答 3

9

您有第三个选择:通过使用属性使其既是属性又是方法:

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

这样您每个实例只需计算一次。Spheroid

您使用什么取决于许多因素;您的 API 需要使用起来有多容易,计算体积的频率,将创建多少个 Spheroid 实例等。如果您在循环中创建一百万个,但只需要其中少数几个的体积,使用属性而不是在__init__.

但是,如果您的班级可以根据音量自行调整;比如说,通过自动调整一个半径,那么 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

    @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用作未调用的任何值(不在()名称后使用)。

于 2013-03-20T18:31:32.840 回答
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
于 2013-03-20T18:32:38.260 回答
1

由于以下原因,我会将音量作为一种方法来实现:

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