[Tutor] design of Point class

Gregory, Matthew matt.gregory at oregonstate.edu
Mon Aug 23 20:36:31 CEST 2010


Steven D'Aprano wrote:
> It would surprise me greatly if numpy didn't already have such a class.

Yes, that is the first place I went looking, but I couldn't find such a class.  I found one project using numpy for geometry objects (geometry-simple, http://code.google.com/p/geometry-simple/), but it doesn't look to be fully fleshed out.

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

Thanks for this help.  I'm curious as to why immutability would be an advantage here (or maybe that's not what you're suggesting).  Typically, I would want to be able to do 'p.x = 10', so subclassing from a list (or numpy nd-array perhaps) would make more sense in my case?

Further, if I use setters, can I still use decorators as you've outlined or do I need to do use 'x = property(get, set)'.  These are all new language constructs that I haven't encountered yet.
  
> 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).

All good ideas, especially the different distance metrics to be defined in Point.  I'm working on implementing these.

> 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

Is there an advantage to doing this?  Wouldn't this make one falsely assume that y was defined and equal to 0?

thanks, matt


More information about the Tutor mailing list