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 _______________________________________________ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor