Re: [Tutor] design of Point class
On Thu, 26 Aug 2010 07:29:42 am Gregory, Matthew wrote: I hope you'll suffer me one more question on this thread. In thinking about creating other distance methods (as you suggest), how best to create a generic enough interface, so that ANY distance metric could be used. It seems like coding the Point class with multiple distance methods is not very flexible, especially if you wanted to iterate over those methods for any two points, e.g. class Point(tuple): def euclidean_distance(self, other): ... def manhattan_distance(self, other): ... def any_other_distance(self, other): ... Would this be a place for a generic get_distance with a DistanceMetric subclass as a parameter, e.g. class DistanceMetric(object): def distance(self, p1, p2): assert 0, 'Must be defined in subclasses' class EuclideanDistMetric(DistanceMetric): def distance(self, p1, p2): ... class Point(tuple): def get_distance(self, other, metric): distance = metric.distance(self, other) return distance That could work, but it seems like a terribly heavyweight solution for a lightweight problem to me. It doesn't save you any work -- you still have to code the distance method, but instead of creating a single method, you have to create an entire class. So it's more work. I believe the simplest solution is to use the bound methods as first class functions: pt = Point(23, 42) # choose a distance if today == Tuesday: dist = pt.manhattan_distance else: dist = pt.euclidean_distance for other in list_of_other_points: print(dist(other)) -- Steven D'Aprano ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
Steven D'Aprano wrote: 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): [snip] 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). I hope you'll suffer me one more question on this thread. In thinking about creating other distance methods (as you suggest), how best to create a generic enough interface, so that ANY distance metric could be used. It seems like coding the Point class with multiple distance methods is not very flexible, especially if you wanted to iterate over those methods for any two points, e.g. class Point(tuple): def euclidean_distance(self, other): ... def manhattan_distance(self, other): ... def any_other_distance(self, other): ... Would this be a place for a generic get_distance with a DistanceMetric subclass as a parameter, e.g. class DistanceMetric(object): def distance(self, p1, p2): assert 0, 'Must be defined in subclasses' class EuclideanDistMetric(DistanceMetric): def distance(self, p1, p2): ... class Point(tuple): def get_distance(self, other, metric): distance = metric.distance(self, other) return distance I'm sure I don't have all my syntax correct. Thanks for continued help. matt ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
Bob Gailer wrote: class PointND(list): def __init__(self, *a_list): super(PointND, self).__init__(a_list) def getSet(ix): def chklen(self): if len(self) ix + 1: raise AttributeError def get(self): chklen(self) return self[ix] def set(self, value): chklen(self) self[ix] = value return property(get, set) def set(self, ix): return s x = getSet(0) y = getSet(1) z = getSet(2) [snip] Bob and Hugo, thanks for enlightening me to class properties. Obviously, I'm still on the learning curve. Bob, I'm not seeing where the outer def set(self, ix) is used. Am I missing something? thanks, matt ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
Hi Steven, Steven D'Aprano wrote: Every time you change the interface of inherited methods, you probably shouldn't. Firstly, it probably breaks the Liskov Substitution Principle. The LSP says, essentially, if you subclass A to make B, you should be able to use a B anywhere you can use an A. (After all, instances of B are also instances of A.) Here's an example of what not to do: class Vehicle: def start(self, key): ignition.insert(key) ignition.turn() # raises exception if out of fuel def go(self, key): self.start(key) self.handbrake = 'off' self.gear = 'drive' self.accelerate() class Truck(Vehicle): # add other truck-like methods class KeylessTruck(Truck): # Some military vehicles are designed to not require keys. # When on a battlefield, there's nothing worse than being # unable to find the car keys! def go(self): self.start() self.handbrake = 'off' self.gear = 'drive' self.accelerate() def start(self): ignition.turn() # Actually a push button, but nevermind. Can you see the problem? If the caller is expecting a Truck, and pass a key to the truck.go() method, they will get an exception if you give them a KeylessTruck instead of a Truck. This is a Bad Thing. Secondly, changing method interfaces is not compatible with multiple inheritance and super(). You can probably get away with it if you stick to single inheritance, but it's still a bad thing to do. Yes, this absolutely makes sense, but I'm less clear on how to solve it. What I would think is that Vehicle shouldn't be defining start and this should be left up to a delegate within the subclasses? Or am I showing my ignorance? matt matt ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
On 8/23/2010 1:09 PM, Gregory, Matthew wrote: Bob Gailer wrote: class PointND(list): def __init__(self, *a_list): super(PointND, self).__init__(a_list) def getSet(ix): def chklen(self): if len(self) ix + 1: raise AttributeError def get(self): chklen(self) return self[ix] def set(self, value): chklen(self) self[ix] = value return property(get, set) def set(self, ix): return s x = getSet(0) y = getSet(1) z = getSet(2) [snip] Bob and Hugo, thanks for enlightening me to class properties. Obviously, I'm still on the learning curve. Bob, I'm not seeing where the outer def set(self, ix) is used. Am I missing something? It should not be there! An accidental leftover. But I notice other problems in my code which I am now attempting to fix. Back to you seen. -- Bob Gailer 919-636-4239 Chapel Hill NC ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
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 ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
I am happier with this: class PointND(list, object): def __init__(self, *a_list): super(PointND, self).__init__(a_list) self.maxIndex = len(self) - 1 def getSet(ix, attName): msg = '%s' object has no attribute '%s' % (p.__class__.__name__, attName) def get(self): if self.maxIndex ix: raise AttributeError, msg return self[ix] def set(self, value): if self.maxIndex ix: raise AttributeError, msg self[ix] = value return property(get, set) x = getSet(0, 'x') y = getSet(1, 'y') z = getSet(2, 'z') p = PointND(1,2,3) assert (p.x, p.y, p.z) == (1, 2, 3) p.x = 6; p.y = 9; p.z = 5 assert (p.x, p.y, p.z) == (6, 9, 5) try: p = PointND(1,2) p.z = 3 except AttributeError: print 'Passed all tests' -- Bob Gailer 919-636-4239 Chapel Hill NC ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
On Tue, 24 Aug 2010 04:36:31 am Gregory, Matthew wrote: I'm curious as to why immutability would be an advantage here (or maybe that's not what you're suggesting). Immutability is *always* an advantage for numeric types. You can use immutable objects as keys in dicts. You can (if you want) optimise the class so that it caches instances, thus saving memory. You can be sure that invariants remain true. If an immutable instance has to be, say, positive, once you've checked that it is positive once you can be sure than no function can change it to be negative. Look at the standard Python numeric types: int, long, float, decimal.Decimal, fractions.Fraction They are all immutable. That should tell you something :) 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? Why do you want to modify points rather than create new ones as needed? In any case, you should not subclass from list. It makes no sense to have pt.sort() or pt.pop() or various other list-like methods. Subclassing list is not the way. If you insist on mutable points, then something like: class MutablePoint(object): def __init__(self, *ordinates): self._ordinates = list(ordinates) def __getitem__(self, i): return self._ordinates[i] def __setitem__(self, i, x): self._ordinates[i] = x and so forth. 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. In Python 2.5, the only way to pass a setter and/or deleter to property is by using the form x = property(getter, setter, deleter). In Python 2.6, properties gain methods that let you do this: @property def x(self): return self._x @x.setter def x(self, value): self._x = value @x.deleter def x(self): del self._x Note that you MUST use the same name (x in the above example) for the getter, setter and deleter. [...] 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? That's why it's an alternative. If you decide that for your application it makes sense to treat coordinates on the XY plane as equivalent to coordinates in the XYZ space with Z=0, that's the simplest way to implement it. -- Steven ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
Gregory, Matthew matt.greg...@oregonstate.edu wrote class Vehicle: def start(self, key): def go(self, key): class Truck(Vehicle): # add other truck-like methods class KeylessTruck(Truck): # Some military vehicles are designed to not require keys. Aside: Most modern high-end cars are keyless too! ;-) def go(self): def start(self): Can you see the problem? If the caller is expecting a Truck, and pass a key to the truck.go() method, they will get an exception if you give them a KeylessTruck instead of a Truck. This is a Bad Thing. Yes, this absolutely makes sense, but I'm less clear on how to solve it. There are several ways. Where you cannot change the original interface - which wrongly assumed that all vehicles need keys - you can introduce a default key parameter in which the method just ignores the key and provides a default value(None?) if not provided. Its messy and I'd definitely comment it to make it clear the key is only to preserve the inherited interface but its probably the simplest option. What I would think is that Vehicle shouldn't be defining start Defining start is probably OK (I can't think of a vehicle that doesn't start in some way or other), but requiring a key is a mistake since many vehicles don't use keys (think rickshaw or trolley bus etc) and this should be left up to a delegate within the subclasses? I'm not sure a delegate would help here, especially if you want to start a list of vehicles. Or am I showing my ignorance? No, just uncovering the challenges of OO design. There is no perfect solution, all models are approximations. Experience tends to build better approximations but never perfect ones. And sometimes breaking the LSP and working round the consequences (try/except...) is the best - ie most pragmatic - solution. But that should be the least favourite choice, try to maintain interfaces if possible. HTH, -- Alan Gauld Author of the Learn to Program web site http://www.alan-g.me.uk/ ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
On 8/20/2010 8:35 PM, bob gailer wrote: After yet more thought it gets even more better: I even added a unit test. class PointND(list): def __init__(self, *a_list): super(PointND, self).__init__(a_list) def getSet(ix): def chklen(self): if len(self) ix + 1: raise AttributeError def get(self): chklen(self) return self[ix] def set(self, value): chklen(self) self[ix] = value return property(get, set) def set(self, ix): return s x = getSet(0) y = getSet(1) z = getSet(2) p = PointND(1,2,3) assert (p.x, p.y, p.z) == (1, 2, 3) p.x = 6; p.y = 9; p.z = 5 assert (p.x, p.y, p.z) == (6, 9, 5) try: p = PointND(1,2) p.z = 3 except AttributeError: print 'Passed all tests' except: print 'some other exception' -- Bob Gailer 919-636-4239 Chapel Hill NC ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
On 8/20/10, Gregory, Matthew matt.greg...@oregonstate.edu 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. Here's a (very rough) first cut at the constructor and a generic distance function for the n-dimensional case: class PointND(object): def __init__(self, a_list): self.a_list = a_list[:] def distance(self, right): assert(len(self.coord) == len(right)) squared_diffs = [(i-j)*(i-j) for (i,j) in zip(self.coord, right)] return math.sqrt(sum(squared_diffs)) But how can I subclass this in such a way to be able to: 1) Have named variables in the 2- and 3-D cases I assume you will have separate subclasses for both 2d and 3d, so just set up each subclass's __init__ to accept the proper amount and order of arguments. 2) Be able to initialize with separate passed values, e.g. 'p = Point2D(3.0, 5.0)' rather than passing in a list class 2dPoint(point): def __init__(self, x, y): self.x=x self.y=y self.points=[x,y] Or am I totally off on thinking this is a good place for inheritance? I would use subclassing. Designed right, this gives you access to common functions like distance but also lets you customize each subclass with specific methods unique to that subclass. Just my 2 cents, though, and I am not too experienced here. Thanks for any help, matt ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor -- Have a great day, Alex (msg sent from GMail website) mehg...@gmail.com; http://www.facebook.com/mehgcap ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
On 8/20/2010 11:45 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. Here's a (very rough) first cut at the constructor and a generic distance function for the n-dimensional case: class PointND(object): def __init__(self, a_list): self.a_list = a_list[:] def distance(self, right): assert(len(self.coord) == len(right)) squared_diffs = [(i-j)*(i-j) for (i,j) in zip(self.coord, right)] return math.sqrt(sum(squared_diffs)) But how can I subclass this in such a way to be able to: 1) Have named variables in the 2- and 3-D cases 2) Be able to initialize with separate passed values, e.g. 'p = Point2D(3.0, 5.0)' rather than passing in a list One class fits all: class PointND(list): def __init__(self, *a_list): super(PointND, self).__init__(a_list) if len(self)= 2: self.x = self[0] if len(self) == 2: self.y = self[1] -- Bob Gailer 919-636-4239 Chapel Hill NC ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
On Fri, Aug 20, 2010 at 10:45 AM, Gregory, Matthew matt.greg...@oregonstate.edu 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. Here's a (very rough) first cut at the constructor and a generic distance function for the n-dimensional case: class PointND(object): def __init__(self, a_list): self.a_list = a_list[:] def distance(self, right): assert(len(self.coord) == len(right)) squared_diffs = [(i-j)*(i-j) for (i,j) in zip(self.coord, right)] return math.sqrt(sum(squared_diffs)) But how can I subclass this in such a way to be able to: 1) Have named variables in the 2- and 3-D cases 2) Be able to initialize with separate passed values, e.g. 'p = Point2D(3.0, 5.0)' rather than passing in a list class Point2D(PointND): def __init__(self, x = 0, y = 0): super(Point2D, self).__init__([x,y]) self.x = 0 self.y = 0 though you wouldn't be able to directly modify the values, or you'll lose the distance function. You'd have to create setter functions, and as such should rename x and y to _x and _y, to indicate that sure you *can* touch these, but really you shouldn't. For the 3d, you'd just add a z param, although to really generalize your ND class you could do this instead: class PointND(object): def __init__(self, x=0, y=0, z=0, a_list=None): if a_list is not None: self.a_list = a_list[:] self.x = x self.y = y self.z = z def coords(self): return [self.x, self.y, self.z] + self.a_list ... Then your subclass takes less effort: class Point2D(PointND): def __init__(self, x=0, y=0): super(Point2D, self).__init__(x,y) and this allows you to access point.x, point.y, and point.z directly. Of course you could also subclass list with ND and just use descriptors for self[0], self[1], and self[2]: http://users.rcn.com/python/download/Descriptor.htm HTH, Wayne ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
Wayne Werner wrote: class Point2D(PointND): def __init__(self, x = 0, y = 0): super(Point2D, self).__init__([x,y]) self.x = 0 self.y = 0 though you wouldn't be able to directly modify the values, or you'll lose the distance function. You'd have to create setter functions, and as such should rename x and y to _x and _y, to indicate that sure you *can* touch these, but really you shouldn't. For the 3d, you'd just add a z param, although to really generalize your ND class you could do this instead: class PointND(object): def __init__(self, x=0, y=0, z=0, a_list=None): if a_list is not None: self.a_list = a_list[:] self.x = x self.y = y self.z = z def coords(self): return [self.x, self.y, self.z] + self.a_list ... Then your subclass takes less effort: class Point2D(PointND): def __init__(self, x=0, y=0): super(Point2D, self).__init__(x,y) and this allows you to access point.x, point.y, and point.z directly. Of course you could also subclass list with ND and just use descriptors for self[0], self[1], and self[2]: http://users.rcn.com/python/download/Descriptor.htm Thanks all for good suggestions. I'm intrigued by the idea of subclassing list (suggested by both Bob and Wayne) and using x, y and z as descriptors to the elements in the list. Obviously, it's important that the descriptors (x,y,z) stay in sync with the list itself so that: p = PointND(1,2,3) p.x = 10 p [10,2,3] From what I understood of the link Wayne sent, I should be able to use __set__ to create this relationship between the labels and the list, but I'm totally lost on how to do this. It seems like x,y,z need to be instances of descriptor objects who have values that are associated with the list. In the mean time, I've overridden __setattr__ to enforce this, but it looks a bit crufty. Any further suggestions are most welcome. class PointND(list): def __init__(self, *a_list): super(PointND, self).__init__(a_list) if len(self) = 3: self.x = self[0] if len(self) = 2 and len(self) = 3: self.y = self[1] if len(self) == 3: self.z = self[2] def __setattr__(self, attr, value): if attr in ('x', 'y', 'z'): self.__dict__[attr] = value if attr == 'x': self[0] = value elif attr == 'y': self[1] = value else: self[2] = value ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
On Fri, Aug 20, 2010 at 12:55 PM, Gregory, Matthew matt.greg...@oregonstate.edu wrote: Wayne Werner wrote: class Point2D(PointND): def __init__(self, x = 0, y = 0): super(Point2D, self).__init__([x,y]) self.x = 0 self.y = 0 though you wouldn't be able to directly modify the values, or you'll lose the distance function. You'd have to create setter functions, and as such should rename x and y to _x and _y, to indicate that sure you *can* touch these, but really you shouldn't. For the 3d, you'd just add a z param, although to really generalize your ND class you could do this instead: class PointND(object): def __init__(self, x=0, y=0, z=0, a_list=None): if a_list is not None: self.a_list = a_list[:] self.x = x self.y = y self.z = z def coords(self): return [self.x, self.y, self.z] + self.a_list ... Then your subclass takes less effort: class Point2D(PointND): def __init__(self, x=0, y=0): super(Point2D, self).__init__(x,y) and this allows you to access point.x, point.y, and point.z directly. Of course you could also subclass list with ND and just use descriptors for self[0], self[1], and self[2]: http://users.rcn.com/python/download/Descriptor.htm Thanks all for good suggestions. I'm intrigued by the idea of subclassing list (suggested by both Bob and Wayne) and using x, y and z as descriptors to the elements in the list. Obviously, it's important that the descriptors (x,y,z) stay in sync with the list itself so that: p = PointND(1,2,3) p.x = 10 p [10,2,3] From what I understood of the link Wayne sent, I should be able to use __set__ to create this relationship between the labels and the list, but I'm totally lost on how to do this. It seems like x,y,z need to be instances of descriptor objects who have values that are associated with the list. In the mean time, I've overridden __setattr__ to enforce this, but it looks a bit crufty. Any further suggestions are most welcome. class PointND(list): def __init__(self, *a_list): super(PointND, self).__init__(a_list) if len(self) = 3: self.x = self[0] if len(self) = 2 and len(self) = 3: self.y = self[1] if len(self) == 3: self.z = self[2] def __setattr__(self, attr, value): if attr in ('x', 'y', 'z'): self.__dict__[attr] = value if attr == 'x': self[0] = value elif attr == 'y': self[1] = value else: self[2] = value perhaps properties could be of some use? from operator import itemgetter, setitem def named_index(index): getter = itemgetter(index) setter = lambda self, val: setitem(self, index, val) return property(getter, setter) class NPoint(list): x = named_index(0) y = named_index(1) z = named_index(2) p = NPoint([3, 4, 50]) print p.x, p.y, p.z p.x = p.y + 13 print p Note that trying to access z when there are not enough items in the list will raise an IndexError, not an AttributeError. You might want to adjust the getter/setter functions a little. Alternatively, I'm not sure if you can add properties in __init__ or __new__, but if not that, you can probably write a metaclass that adds in the right properties based on list length. Hugo ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
On 8/20/2010 1:55 PM, Gregory, Matthew wrote: Wayne Werner wrote: class Point2D(PointND): def __init__(self, x = 0, y = 0): super(Point2D, self).__init__([x,y]) self.x = 0 self.y = 0 though you wouldn't be able to directly modify the values, or you'll lose the distance function. You'd have to create setter functions, and as such should rename x and y to _x and _y, to indicate that sure you *can* touch these, but really you shouldn't. For the 3d, you'd just add a z param, although to really generalize your ND class you could do this instead: class PointND(object): def __init__(self, x=0, y=0, z=0, a_list=None): if a_list is not None: self.a_list = a_list[:] self.x = x self.y = y self.z = z def coords(self): return [self.x, self.y, self.z] + self.a_list ... Then your subclass takes less effort: class Point2D(PointND): def __init__(self, x=0, y=0): super(Point2D, self).__init__(x,y) and this allows you to access point.x, point.y, and point.z directly. Of course you could also subclass list with ND and just use descriptors for self[0], self[1], and self[2]: http://users.rcn.com/python/download/Descriptor.htm Thanks all for good suggestions. I'm intrigued by the idea of subclassing list (suggested by both Bob and Wayne) and using x, y and z as descriptors to the elements in the list. Obviously, it's important that the descriptors (x,y,z) stay in sync with the list itself so that: p = PointND(1,2,3) p.x = 10 p [10,2,3] From what I understood of the link Wayne sent, I should be able to use __set__ to create this relationship between the labels and the list, but I'm totally lost on how to do this. It seems like x,y,z need to be instances of descriptor objects who have values that are associated with the list. In the mean time, I've overridden __setattr__ to enforce this, but it looks a bit crufty. Any further suggestions are most welcome. class PointND(list): def __init__(self, *a_list): super(PointND, self).__init__(a_list) if len(self)= 3: self.x = self[0] if len(self)= 2 and len(self)= 3: self.y = self[1] if len(self) == 3: self.z = self[2] def __setattr__(self, attr, value): if attr in ('x', 'y', 'z'): self.__dict__[attr] = value if attr == 'x': self[0] = value elif attr == 'y': self[1] = value else: self[2] = value class PointND(list): def __init__(self, *a_list): super(PointND, self).__init__(a_list) def get(self, ix): if len(self)= ix + 1: return self[ix] else: raise AttributeError def set(self, ix, value): if len(self)= ix + 1: self[ix] = value else: raise AttributeError def getx(self): return self.get(0) def setx(self, value): self.set(0, value) x = property(getx, setx) def gety(self): return self.get(1) def sety(self, value): self.set(1, value) y = property(gety, sety) # repeat for z -- Bob Gailer 919-636-4239 Chapel Hill NC ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
On 8/20/2010 1:55 PM, Gregory, Matthew wrote: Wayne Werner wrote: class Point2D(PointND): def __init__(self, x = 0, y = 0): super(Point2D, self).__init__([x,y]) self.x = 0 self.y = 0 though you wouldn't be able to directly modify the values, or you'll lose the distance function. You'd have to create setter functions, and as such should rename x and y to _x and _y, to indicate that sure you *can* touch these, but really you shouldn't. For the 3d, you'd just add a z param, although to really generalize your ND class you could do this instead: class PointND(object): def __init__(self, x=0, y=0, z=0, a_list=None): if a_list is not None: self.a_list = a_list[:] self.x = x self.y = y self.z = z def coords(self): return [self.x, self.y, self.z] + self.a_list ... Then your subclass takes less effort: class Point2D(PointND): def __init__(self, x=0, y=0): super(Point2D, self).__init__(x,y) and this allows you to access point.x, point.y, and point.z directly. Of course you could also subclass list with ND and just use descriptors for self[0], self[1], and self[2]: http://users.rcn.com/python/download/Descriptor.htm Thanks all for good suggestions. I'm intrigued by the idea of subclassing list (suggested by both Bob and Wayne) and using x, y and z as descriptors to the elements in the list. Obviously, it's important that the descriptors (x,y,z) stay in sync with the list itself so that: p = PointND(1,2,3) p.x = 10 p [10,2,3] From what I understood of the link Wayne sent, I should be able to use __set__ to create this relationship between the labels and the list, but I'm totally lost on how to do this. It seems like x,y,z need to be instances of descriptor objects who have values that are associated with the list. In the mean time, I've overridden __setattr__ to enforce this, but it looks a bit crufty. Any further suggestions are most welcome. class PointND(list): def __init__(self, *a_list): super(PointND, self).__init__(a_list) if len(self)= 3: self.x = self[0] if len(self)= 2 and len(self)= 3: self.y = self[1] if len(self) == 3: self.z = self[2] def __setattr__(self, attr, value): if attr in ('x', 'y', 'z'): self.__dict__[attr] = value if attr == 'x': self[0] = value elif attr == 'y': self[1] = value else: self[2] = value After more thought it gets even better: class PointND(list): def __init__(self, *a_list): super(PointND, self).__init__(a_list) def get(ix): def g(self): if len(self) = ix + 1: return self[ix] else: raise AttributeError return g def set(ix): def s(self, value): if len(self) = ix + 1: self[ix] = value else: raise AttributeError return s x = property(get(0), set(0)) y = property(get(1), set(1)) z = property(get(2), set(2)) -- Bob Gailer 919-636-4239 Chapel Hill NC ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
Gregory, Matthew matt.greg...@oregonstate.edu wrote I typically deal with geographic (either 2D or 3D) data, yet there are occasions when I need n-dimensional points as well. Thats OK. 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. Thats OK too but adds some overhead. Remember that inheritance should be based on behaviour not data so determine the common methods of a Point. Write the ND version of those methods. Then create subclasses for 2D and 3D which convert the named args to a list equivalent then call the superclass ND versions. Every time you change the interface of inherited methods you create for yourself extra work in converting types to match the superclass. But that is often easier than rewriting the methods from scratch. Just remember that to get the best out of polymorphism - the primary reason for inheritance - you should be able to mix n match instances of the subclasses with instances of the superclass - the Liskov Substitution Principle(LSP) - and if you break that you lose for yourself much of the power of inheritance. HTH, -- Alan Gauld Author of the Learn to Program web site http://www.alan-g.me.uk/ Here's a (very rough) first cut at the constructor and a generic distance function for the n-dimensional case: class PointND(object): def __init__(self, a_list): self.a_list = a_list[:] def distance(self, right): assert(len(self.coord) == len(right)) squared_diffs = [(i-j)*(i-j) for (i,j) in zip(self.coord, right)] return math.sqrt(sum(squared_diffs)) But how can I subclass this in such a way to be able to: 1) Have named variables in the 2- and 3-D cases 2) Be able to initialize with separate passed values, e.g. 'p = Point2D(3.0, 5.0)' rather than passing in a list Or am I totally off on thinking this is a good place for inheritance? Thanks for any help, matt ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
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
Re: [Tutor] design of Point class
On Sat, 21 Aug 2010 08:08:56 am Alan Gauld wrote: Every time you change the interface of inherited methods you create for yourself extra work in converting types to match the superclass. But that is often easier than rewriting the methods from scratch. Every time you change the interface of inherited methods, you probably shouldn't. Firstly, it probably breaks the Liskov Substitution Principle. The LSP says, essentially, if you subclass A to make B, you should be able to use a B anywhere you can use an A. (After all, instances of B are also instances of A.) Here's an example of what not to do: class Vehicle: def start(self, key): ignition.insert(key) ignition.turn() # raises exception if out of fuel def go(self, key): self.start(key) self.handbrake = 'off' self.gear = 'drive' self.accelerate() class Truck(Vehicle): # add other truck-like methods class KeylessTruck(Truck): # Some military vehicles are designed to not require keys. # When on a battlefield, there's nothing worse than being # unable to find the car keys! def go(self): self.start() self.handbrake = 'off' self.gear = 'drive' self.accelerate() def start(self): ignition.turn() # Actually a push button, but nevermind. Can you see the problem? If the caller is expecting a Truck, and pass a key to the truck.go() method, they will get an exception if you give them a KeylessTruck instead of a Truck. This is a Bad Thing. Secondly, changing method interfaces is not compatible with multiple inheritance and super(). You can probably get away with it if you stick to single inheritance, but it's still a bad thing to do. -- Steven D'Aprano ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] design of Point class
On Sat, 21 Aug 2010 11:50:40 am Steven D'Aprano wrote: On Sat, 21 Aug 2010 08:08:56 am Alan Gauld wrote: Every time you change the interface of inherited methods you create for yourself extra work in converting types to match the superclass. But that is often easier than rewriting the methods from scratch. Every time you change the interface of inherited methods, you probably shouldn't. Oops, that's a generic you, not Alan. After all, he went on to mention Liskov in the very next paragraph. -- Steven D'Aprano ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor