[Tutor] design of Point class
Steven D'Aprano
steve at pearwood.info
Sat Aug 21 03:24:28 CEST 2010
On Sat, 21 Aug 2010 01:45:18 am Gregory, Matthew wrote:
> Hi all,
>
> I often struggle with object design and inheritance. I'd like
> opinions on how best to design a Point class to be used in multiple
> circumstances.
>
> I typically deal with geographic (either 2D or 3D) data, yet there
> are occasions when I need n-dimensional points as well. My thought
> was to create a superclass which was an n-dimensional point and then
> subclass that to 2- and 3-dimensional cases. The rub to this is that
> in the n-dimensional case, it probably makes most sense to store the
> actual coordinates as a list whereas with the 2- and 3-D cases, I
> would want 'named' variables, such as x, y, z.
It would surprise me greatly if numpy didn't already have such a class.
Other than using numpy, probably the simplest solution is to just
subclass tuple and give it named properties and whatever other methods
you want. Here's a simple version:
class Point(tuple):
def __new__(cls, *args):
if len(args) == 1 and isinstance(args, tuple):
args = args[0]
for a in args:
try:
a+0
except TypeError:
raise TypeError('ordinate %s is not a number' % a)
return super(Point, cls).__new__(cls, args)
@property
def x(self):
return self[0]
@property
def y(self):
return self[1]
@property
def z(self):
return self[2]
def dist(self, other):
if isinstance(other, Point):
if len(self) == len(other):
sq_diffs = sum((a-b)**2 for (a,b) in zip(self, other))
return math.sqrt(sq_diffs)
else:
raise ValueError('incompatible dimensions')
raise TypeError('not a Point')
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, tuple(self))
class Point2D(Point):
def __init__(self, *args):
if len(self) != 2:
raise ValueError('need exactly two ordinates')
class Point3D(Point):
def __init__(self, *args):
if len(self) != 3:
raise ValueError('need exactly three ordinates')
These classes gives you:
* immutability;
* the first three ordinates are named x, y and z;
* any ordinate can be accessed by index with pt[3];
* distance is only defined if the dimensions are the same;
* nice string form;
* input validation.
What it doesn't give you (yet!) is:
* distance between Points with different dimensions could easily be
defined just by removing the len() comparison. zip() will
automatically terminate at the shortest input, thus projecting the
higher-dimension point down to the lower-dimension point;
* other distance methods, such as Manhattan distance;
* a nice exception when you as for (say) pt.z from a 2-D point, instead
of raising IndexError;
* point arithmetic (say, adding two points to get a third).
But you can't expect me to do all your work :)
An alternative would be to have the named ordinates return 0 rather than
raise an error. Something like this would work:
@property
def y(self):
try: return self[1]
except IndexError: return 0
--
Steven D'Aprano
More information about the Tutor
mailing list