Python - Inherited methods break when overriding __init__ - python

I have a geometric base class ExtrudedSurface and a child class Cylinder, which is a 'kind of' extruded surface.
The base class has a method to translate itself (not in-place) by constructing a modified version of itself.
I would like to re-use this method by the child class Cylinder, and have it return a new, translated Cylinder. However, as implemented now this does not work because Cylinder has a different __init__ signature which is called in translate.
What is the best way to achieve this? How can Cylinder use the inherited method translate and return a new Cylinder object?
EDIT 1: I think it has to do with LSP violation but I'm not sure.
class ExtrudedSurface:
def __init__(self, profile: Curve, direction: Vector, length: float):
"""
An extruded surface geometry created by sweeping a 3D 'profile' curve along a
'direction' for a given 'length'.
"""
self.profile = profile
self.direction = direction.normalize()
self.length = length
def translate(self, translation: Vector) -> ExtrudedSurface:
"""Return a translated version of itself."""
return self.__class__(
self.profile.translate(translation),
self.length,
self.direction
)
class Cylinder(ExtrudedSurface):
def __init__(self, point: Point, direction: Vector, radius: float, length: float):
"""Cylinder surface.
Args:
point: Center point of extruded circle.
direction: Direction vector of the cylinder (axis).
radius: Cylinder radius.
length: Extrusion length in 'direction'.
"""
direction = direction.normalize()
profile = Circle(point, direction, radius)
super().__init__(profile, direction, length)

Short story short: the by-the-book approach there is to override the translate() method, as well, and call the updated constructor from there.
Now, you can refactor your class initialization and separate attribute setting from other needed actions, and then create a new class-method to clone an instance with all the attributes from a first instance, and just call this initialization, if needed.
If no initialization is needed, just a "clone" method is needed. If you happen to call this method __copy__, then you can use the copy.copy call for that, which is almost as if it was an operator in Python, and it can, by itself, have value for your end-users.
Moreover -- if your class requires no initialization besides setting plain attributes, and no calculations at all, copy.copy will just work out of the box with your instances - no extra __copy__ method needed:
from copy import copy
class ExtrudedSurface:
def __init__(self, profile: Curve, direction: Vector, length: float):
"""
An extruded surface geometry created by sweeping a 3D 'profile' curve along a
'direction' for a given 'length'.
"""
self.profile = profile
self.direction = direction.normalize()
self.length = length
def translate(self, translation: Vector) -> ExtrudedSurface:
"""Return a translated version of itself."""
new_profile = self.profile.translate(translation)
new_instance = copy(self)
new_instance.profile = new_profile
return new_instance
class Cylinder(ExtrudedSurface):
def __init__(self, point: Point, direction: Vector, radius: float, length: float):
...
Just attempt to the fact that copy will not recursively copy the attributes, so, if the self.direction vector is a mutable object in the framework you are using, this will keep both clones bound to the same vector, and if it changes in the original, that change will be reflected in the clone. By the nature of your code I am assuming everything is immutable there and all transforms create new instances: then this will work. Otherwise, you should also copy the original .direction attribute into the new instance.
In time: yes, that is an LSP violation - but I always think that LSP is given more importance than it should when it come to practical matters.
The more generic code I described, should your initialization be more complex would be:
class Base:
def __init__(self, base_args):
#code to set initial attributes
...
# call inner init with possible sideeffects:
self.inner_init()
def inner_init(self):
# code with side effects (like, attaching instance to a container)
# should be written in order to be repeat-proof - calls on the same instance have no effect
...
def __copy__(self):
new_instance = self.__class__.new()
new_instance.__dict__.update(self.__dict__)
self.inner_init()

Related

Python: overlapping (non exclusive) inheritance to have methods available based on instance parameters

I want to have certain attributes & methods only available in a class instance, if the parameters meet certain conditions. The different cases are not exclusive. I already have a working solution (incl. the suggestion from ShadowRanger):
class polygon():
def __new__(cls, vert, axis=None):
triangle = vert == 3
solidrev = axis is not None
if triangle and not solidrev:
return super().__new__(_triangle)
elif solidrev and not triangle:
return super().__new__(_solid)
elif solidrev and triangle:
return super().__new__(_triangle_solid)
else:
return super().__new__(cls)
def __init__(self, vert, axis=None):
self.polygon_attribute = 1
def polygon_method(self):
print('polygon')
class _triangle(polygon):
def __init__(self, vert, axis=None):
super().__init__(vert, axis)
self.triangle_attribute = 2
def triangle_method(self):
print('triangle')
class _solid(polygon):
def __init__(self, vert, axis):
super().__init__(vert, axis)
self.solid_attribute = 3
def solid_method(self):
print('solid of revolution')
class _triangle_solid(_triangle, _solid):
def __init__(self, vert, axis):
super().__init__(vert, axis)
Availability of attributes & methods depends on the instance parameters:
The attributes & methods from the base class should always be available.
If the first parameter equals 3, the attributes & methods from subclass _triangle should be available.
If the second parameter is defined, the attributes & methods from subclass _solid should be available.
All combinations:
P = polygon(2)
P = polygon(2,axis=0)
P = polygon(3)
P = polygon(3,axis=0)
Is there a more elegant way to do this? In the ideal case, I want to get rid of the _triangle_solid class. Also, I don't get why I need to define the default argument for axis in some cases but not all of them.
Full project: https://github.com/gerritnowald/polygon
This is an example of trying to overdo use of inheritance. Inheritance makes logical sense when there is an "is a" relationship between the child and its parent class. A triangle is a polygon, so no problems there; it's a reasonable inheritance chain. A solid of revolution, while possibly built from a polygon, is not a polygon, and trying to wedge that into the inheritance hierarchy is creating problems. It's even worse because a solid of revolution may not even be defined in terms of a polygon at all.
I'd strongly recommend defining your solids of revolution with an attribute representing whatever is being revolved to produce it, not as a subclass of that revolved figure.
All that said, polygon itself should not be responsible for knowing all of its subclasses, and if it is, it should still be the parent of a triangle. Your design as currently rendered has a polygon class that nothing is an instance of; the __new__ is returning something that is not a polygon, and that's confusing as heck. You can write the hierarchy in a safer, if still not idiomatic OO way, by doing:
# Tweaked name; it's not just the base anymore; using PEP8 class name capitalization rules
class Polygon:
def __new__(cls, vert, *args, **kwargs): # Accept and ignore the arguments we don't care
# about, __init__ will handle them
if vert == 3:
return super().__new__(Triangle)
else:
return super().__new__(cls)
def __init__(self, vert, axis):
self.polygon_attribute = 1
def polygon_method(self):
print('polygon')
class Triangle(Polygon):
def __init__(self, vert, axis):
super().__init__(vert, axis)
self.triangle_attribute = 2
def triangle_method(self):
print('triangle')
t = Polygon(3, None)
p = Polygon(4, None)
print(type(t), type(p))
# Indicates t is a Triangle, p is a Polygon
Try it online!

Subclass Initialization Issue with Data not from Superclass

I'm having an issue trying to make a rectangle class that is subclassed from a Points class, which (bare with me) is subclassed from nd.array.
I'm aware (or so I think) of the nuances of subclassing an nd.array. What i'm trying to achieve is being able to construct a rectangle with a center point, height, width, and rotation and not need to directly feed in discrete points. Below is my implementation of the rectangle constructor, the #init_args wrapper takes type hinted inputs and calls the class constructor which works. It appears that it isn't even getting into the __init__ method and jumps straight to the Points __new__() method.
class Points(np.ndarray):
def __new__(cls, list_of_points):
if type(list_of_points) == 'numpy.ndarray':
obj = list_of_points
else:
obj = np.vstack(list_of_points).view(cls)
return obj
class Rectangle(Points):
#init_args
def __init__(self, rect_attributes: RectAttributes):
self.points = np.zeros((4, 2))
print('in rect constructor')
self.attributes = rect_attributes
self.construct_rect_from_attributes()
super().__init__(self.points)
I don't think I really need the super().__init__ call, I was just hoping that it would delay the Points constructor. What I really need is just to be able to initialize the rectangle with attributes that aren't contained in the superclass.
The comment of chepner is make sense. Another thing that's confusing to me as that you want Rectangle to be a subclass of ndarray, yet you assign to it a self.points (initialized to zeros) of a different ndarray. ndarray.__new__ itself constructs the array and its data, so your Rectangle class is essentially already constructed from the list_of_points passed to Points.__new__.
Meanwhile you have a self.points that has no association with the points passed to the constructor.
From your example it's not clear what rect_attributes is supposed to be or what construct_rect_from_attributes() is supposed to do. But I think what you probably want is something like this (and IMO you should allow construction of Rectangle from a list of points as well, but that's up to whatever your requirements are):
class Rectangle(Points):
# put in the type hints anything else array-like
def __new__(cls, data: Union[list, np.ndarray, RectAttributes]):
if isinstance(data, RectAttributes):
# assuming you want to store the original attributes somewhere
self.attributes = data
# rect_from_attributes should be a classmethod and
# should return an array of points
data = cls.rect_from_attributes(data)
else:
# a default value if the rect was constructed another
# way, or inversely construct a RectAttributes from points
self.attributes = ...
return super().__new__(cls, data)

OOP : Trying to design a good class structure

Follow up code snippet review question I posted: https://codereview.stackexchange.com/questions/143576/oop-designing-class-inheritances
This is homework so off the bat I am not asking to do it for me but to clarify for me so that I understand.
Here is what the assignment said about the program I have to write:
Required:
Write an OO Python Program showing how your sub-classes inherit from the super-class(es) You can have one class (preferred but not a must) or two or more super classes.
What I have to do is write a oo python program showing sub-classing inherited by a class or two or more superclasses. The program has to be about shapes. As an example of what I mean by that is:
Shape: Square
Attributes:
length
width
Methods:
area
perimeter
I have more shapes of course but from that I find the common attributes and methods from all the shapes and make superclasses and sup-classes.
My super classes are like: 2dShapes, circles and 3dShapes. My sub-classes are like length and width. My methods are area and perimeter. Note I am rambling at this point. The code snippet below does not show this instead I was thinking about making a superclass for attributes and methods and than sub-classes for the shapes? maybe?
Question: is this a good class structure? Is there a better way to structure the classes here? Here is an example of what I'm thinking about how to do this.
class Shapes(attributes):
def __init__(self):
attributes.__init__(self)
# not sure how to go on to make the attributes like height and length, radius ect. Maybe like this???
def height(self):
raise NotImplementedError # again this program is not supose to actually do anything. The program is just for us to understand inheritance with classes and super classes.
class 2dShapes(Shapes):
class Square(self):
def height(self):
# ???
So at this point I am so confused about where to start. Also I am super new to python so be gentle to me :p
class Vehicle(object):
#class variable shared between all instances of objects.
number_of_vehicles = 0
def __init__(self,length,width,color,wheels):
self.length = length
self.width = width
self.color = color
self.wheels = wheels
Vehicle.number_of_vehicles += 1 #increasing class varaible count
def get_length(self):
print("I am %d meters long!"%self.length)
def get_wdith(self):
print("I am %d meters wide!"%self.width)
def get_color(self):
print("My color is %s!"%self.color)
def get_wheels(self):
print("I have %d number of wheels"%self.wheels)
#calling my methods so I don't need to call each of their own
def get_stats(self):
self.get_length()
self.get_wheels()
self.get_wdith()
self.get_color()
def honk(self):
print("beep beep")
class Car(Vehicle):
def vroom(self):
print("Cars go vroom vroom")
class Cooper(Car):
def drift(self):
print("Mini Coopers can drift well")
class Airplanes(Vehicle):
def fly(self):
print("weeeeee I'm flying")
class Tank(Vehicle):
#custom init because tanks have guns!~
#taking the gun size and tossing the rest of the arguments to the parent.
#if the parent doesn't find a __init__ it will keep going up until one is found or unelss we call it.
#Here we made a new __init__ so it doesn't go looking for one, but we call super() which is calling for the
#parent's __init__
def __init__(self,gun_size,*args):
self.gun_size = gun_size
super(Tank,self).__init__(*args)
def fire(self):
print("pew pew pew")
#I have my custom get_stats but still calls parent's one so I don't repeat code.
def get_stats(self):
print("my gun is this big: %d " %self.gun_size)
super(Tank,self).get_stats()
a = Cooper(150,150,"blue",4)
a.drift()
a.vroom()
a.honk()
a.get_stats()
print(a.number_of_vehicles)
b = Airplanes(200,150,"white",2)
b.fly()
print(b.number_of_vehicles)
c = Tank(500,500,250,"Green",18)
c.fire()
print(c.number_of_vehicles)
c.get_stats()
Outputs:
Mini Coopers can drift well
Cars go vroom vroom
beep beep
I am 150 meters long!
I have 4 number of wheels
I am 150 meters wide!
My color is blue!
1 #vehicle count
weeeeee I'm flying # start of plan section
2 #vehicle count
pew pew pew #start of tank section
3 #vehicle count
my gun is this big: 500
I am 500 meters long!
I have 18 number of wheels
I am 250 meters wide!
My color is Green!
So the point of this post was to show you relationship of inheritance.
We have a base class called Vehicle which is a sub class of object. Don't worry about object if you want you can read up on it.
Vehicle class has some attibutes that all vehicles would have, length, width, color, wheels. It also have a class variable called number_of_vehicles which keeps track of how many object instances Vehicle has, basically how many Vehicles we "made". We also have some class methods, that access and uses the attributes we defined in __init__. You can do math on them and what not but for now we are just using them as print statements to show they work. We have a special class method that calls other methods in the same class. So get_stats calls the other get_x methods in the instance. This allows me to call those 4 methods with just "one" method call from my object, see a.get_stats(). We can still call the other methods on it's own like get_color.
We have a sub class called Car which is a vehicle, so we inherit it. Only cars can go vroom, but all cars can go vroom so we have a vroom method only at the car level. The trick is to think what does this class have that is unique to only instances of this class, and if not, can I put it in the parent's class. All vehicles have wheels and so on, but not all vehicles can go vroom (for this example only).
We have a sub class of Car, the cooper (mini cooper), which only it can drift (once again for this example only in real life the drift method would go in vehicle cause all vehicles can draft but bear with me). So this cooper is the only car that can drift so it's down here instead of in the Car class.
Tank's sub class is interesting. Here we have the basic of a vehicle but we have something new, a gun! So our vehicle class can't handle a gun, so we have to make a new __init__ method. We assign the object variable gun_size and then pass the rest of the tank's attribute to Vehicle's __init__ since the rest of the attributes are the same as Vehicle. We call super(Tank,self).__init__(*args) which is basically saying, I am a tank, this is me, please handle the rest of my attribute, parent of mine. Since tanks have a special attribute of a gun, we have to modify our get_stats method, to deal with the gun_size attribute, but the rest of the stats on the tank are the same as vehicle, so we just call our parents to handle the rest after we deal with our gun.
Now I know this is a very silly example, but I do hope you find some useful information in here. There are other majors points I haven't touched upon but this is a starting point. So back to your original question. Think abstract, the highest level would be a shape, then Rectangles are a type of shape so they would inherit it. Squares are special rectangles, so they would inherit rectangles and so on.
If you have questions don't hesitate to ask.
I do not want to give answers that are too specific, because homework, but here is an example which I think might orient you in the right direction.
In Object Oriented Programming, there is the concept of polymorphism: when instances of many different subclasses are related by some common superclass. You often see it explained as "a B is an A", like in "a Pear is a Fruit", "a Cat is an Animal", "a Triangle is a Shape". Subclasses all share a common set of methods and members present in the superclass, but their implementation of these methods can vary.
Here is an example (sorry, C style, not a Python person) with animals. The method hearNoise() accepts an animal, but will also work correctly if a subtype is passed to it:
abstract class Animal {
abstract String makeNoise();
}
class Cat extends Animal {
String makeNoise() {
return "Meow!";
}
}
class Dog extends Animal {
String makeNoise() {
return "Woof!";
}
}
void hearNoise(Animal a) {
println(a.makeNoise());
}
int main() {
hearNoise(new Cat()); //returns "Meow!"
hearNoise(new Dog()); //returns "Woof!"
}
The same principles can be applied to geometric shapes. They all have common methods and members : their perimeter, their area, their color, etc. When you have a shape, you can expect with 100% certitude you will be able to call a method to calculate a perimeter. However, the implementation, the way that specific shape subclass handles perimeter calculation, is what differs from shape to shape.
Let's start with the Shapes class. In the __init__ , you have to set the attributes. First you need 2 parameters other than self. You can use the same names as the attributes or pick different names.
class Shapes(attributes):
def __init__(self, length, width):
self.length = length
self.width = width
If you want to inherit from Shapes class, other than putting it in () in 2dShapes class definition, you have to call the __init__ of the Shapes class and pass a reference and other parameters to it, like this:
class 2dShapes(Shapes):
def __init__(self, length, width):
Shapes.__init__(self, length, width)
If you want to use a method of Shapes in 2dShapes, you have to call it just like how we did for the __init__, lets say there is a method called area() in Shapes. In the 2dShapes, you can access it by Shapes.area(self). Here is an example:
class Shapes(attributes):
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
class 2dShapes(Shapes):
def __init__(self, length, width):
Shapes.__init__(self, length, width)
def area(self):
return Shapes.area(self)
One of the big ideas of using inheritance is to be able to re-use code.. If you have a large body of functions that are the same for several classes, having one parent class that holds those functions allows you to only write them once, and to modify them all at the same time. for example, if you want to implement classes for the shapes: Square, Rectangle, and Parallelogram, you could create a parent class: Quadrangle that contains things like a generic area or perimeter functions:
class quadrangle(object):
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
def perimeter(self):
return 2*self.length + 2*self.width
class square(quadrangle):
def __init__(self, length):
super(square, self).__init__(length, length) #creates a quadrangle with equal lenght and width
class rectangle(quadrangle): #renamed for convienience sake but otherwise the same
pass
class parallelogram(quadrangle):
def __init__(self, length, width, angle): #angle in radians
self.angle = angle
super(parallelogram, self).__init__(length, width)
def perimeter(self): #override parent's perimiter
#do some math
return 5
Be careful not to confuse "attributes" with "methods". There is an #property syntax which wraps attribute access within a method call, but you should ignore that for now.
class Shapes(attributes):
def __init__(self):
For this part, just absorb the arguments here. e.g:
def __init__(self, *args, **kwargs):
pass
Reason being, that you are only using Shapes to pass them to a subclass. *args absorbs lists of named arguments, while **kwargs absorbs dictionaries. So this init() will accept my_shapes_instance = Shapes(length, width, height) because it has *args, and it would accept Shapes(length, width, height, {'cost': '10'}) because it has **kwargs as well.
If it were __init__(length, width, height) and you passed (length, width, height, color) then it would not work. But if you use *args then it will accept anything. Will it use all of these arguments? Only if you define that it does.
You can ignore **kwargs for now since you are not initializing these objects with dictionaries.
attributes.__init__(self)
# not sure how to go on to make the attributes like height and length, radius ect. Maybe like this???
def height(self):
raise NotImplementedError # again this program is not supose to actually do anything. The program is just for us to understand inheritance with classes and super classes.
What you have done above is define a method "height", not an attribute "height". What you want is more like this:
def __init__(self, height):
self.height = height
Even better is this, but do it in the Square subclass:
class Square(Shapes):
def __init__(self, height, *args, **kwargs):
super(Square, self).__init__(height, *args, **kwargs)
self.height = height
Now you can subclass Square with Rectangle, adding in new args as you go. Just follow a similar init pattern as above. Rectangle will not need you to add a height method, since it is already available from the parent.

How do I downcast in python

I have two classes - one which inherits from the other. I want to know how to cast to (or create a new variable of) the sub class. I have searched around a bit and mostly 'downcasting' like this seems to be frowned upon, and there are some slightly dodgy workarounds like setting instance.class - though this doesn't seem like a nice way to go.
eg.
http://www.gossamer-threads.com/lists/python/python/871571
http://code.activestate.com/lists/python-list/311043/
sub question - is downcasting really that bad? If so why?
I have simplified code example below - basically i have some code that creates a Peak object after having done some analysis of x, y data. outside this code I know that the data is 'PSD' data power spectral density - so it has some extra attributes. How do i down cast from Peak, to Psd_Peak?
"""
Two classes
"""
import numpy as np
class Peak(object) :
"""
Object for holding information about a peak
"""
def __init__(self,
index,
xlowerbound = None,
xupperbound = None,
xvalue= None,
yvalue= None
):
self.index = index # peak index is index of x and y value in psd_array
self.xlowerbound = xlowerbound
self.xupperbound = xupperbound
self.xvalue = xvalue
self.yvalue = yvalue
class Psd_Peak(Peak) :
"""
Object for holding information about a peak in psd spectrum
Holds a few other values over and above the Peak object.
"""
def __init__(self,
index,
xlowerbound = None,
xupperbound = None,
xvalue= None,
yvalue= None,
depth = None,
ampest = None
):
super(Psd_Peak, self).__init__(index,
xlowerbound,
xupperbound,
xvalue,
yvalue)
self.depth = depth
self.ampest = ampest
self.depthresidual = None
self.depthrsquared = None
def peakfind(xdata,ydata) :
'''
Does some stuff.... returns a peak.
'''
return Peak(1,
0,
1,
.5,
10)
# Find a peak in the data.
p = peakfind(np.random.rand(10),np.random.rand(10))
# Actually the data i used was PSD -
# so I want to add some more values tot he object
p_psd = ????????????
edit
Thanks for the contributions.... I'm afraid I was feeling rather downcast(geddit?) since the answers thus far seem to suggest I spend time hard coding converters from one class type to another. I have come up with a more automatic way of doing this - basically looping through the attributes of the class and transfering them one to another. how does this smell to people - is it a reasonable thing to do - or does it spell trouble ahead?
def downcast_convert(ancestor, descendent):
"""
automatic downcast conversion.....
(NOTE - not type-safe -
if ancestor isn't a super class of descendent, it may well break)
"""
for name, value in vars(ancestor).iteritems():
#print "setting descendent", name, ": ", value, "ancestor", name
setattr(descendent, name, value)
return descendent
You don't actually "cast" objects in Python. Instead you generally convert them -- take the old object, create a new one, throw the old one away. For this to work, the class of the new object must be designed to take an instance of the old object in its __init__ method and do the appropriate thing (sometimes, if a class can accept more than one kind of object when creating it, it will have alternate constructors for that purpose).
You can indeed change the class of an instance by pointing its __class__ attribute to a different class, but that class may not work properly with the instance. Furthermore, this practice is IMHO a "smell" indicating that you should probably be taking a different approach.
In practice, you almost never need to worry about types in Python. (With obvious exceptions: for example, trying to add two objects. Even in such cases, the checks are as broad as possible; here, Python would check for a numeric type, or a type that can be converted to a number, rather than a specific type.) Thus it rarely matters what the actual class of an object is, as long as it has the attributes and methods that whatever code is using it needs.
See following example. Also, be sure to obey the LSP (Liskov Substitution Principle)
class ToBeCastedObj:
def __init__(self, *args, **kwargs):
pass # whatever you want to state
# original methods
# ...
class CastedObj(ToBeCastedObj):
def __init__(self, *args, **kwargs):
pass # whatever you want to state
#classmethod
def cast(cls, to_be_casted_obj):
casted_obj = cls()
casted_obj.__dict__ = to_be_casted_obj.__dict__
return casted_obj
# new methods you want to add
# ...
This isn't a downcasting problem (IMHO). peekfind() creates a Peak object - it can't be downcast because its not a Psd_Peak object - and later you want to create a Psd_Peak object from it. In something like C++, you'd likely rely on the default copy constructor - but that's not going to work, even in C++, because your Psd_Peak class requires more parameters in its constructor. In any case, python doesn't have a copy constructor, so you end up with the rather verbose (fred=fred, jane=jane) stuff.
A good solution may be to create an object factory and pass the type of Peak object you want to peekfind() and let it create the right one for you.
def peak_factory(peak_type, index, *args, **kw):
"""Create Peak objects
peak_type Type of peak object wanted
(you could list types)
index index
(you could list params for the various types)
"""
# optionally sanity check parameters here
# create object of desired type and return
return peak_type(index, *args, **kw)
def peakfind(peak_type, xdata, ydata, **kw) :
# do some stuff...
return peak_factory(peak_type,
1,
0,
1,
.5,
10,
**kw)
# Find a peak in the data.
p = peakfind(Psd_Peak, np.random.rand(10), np.random.rand(10), depth=111, ampest=222)

storing list/tuples in sqlite database with sqlalchemy

I have a class that holds the size and position of something I draw to the screen. I am using sqlalchemy with a sqlite database to persist these objects. However, the position is a 2D value (x and y) and I'd like to have a convienent way to access this as
MyObject.pos # preferred, simpler interface
# instead of:
MyObject.x
MyObject.y # inconvenient
I can use properties but this solution isn't optimal since I cannot query based on the properties
session.query(MyObject).filter(MyObject.pos==some_pos).all()
Is there some way to use collections or association proxies to get the behavior I want?
If you are using PostGIS (Geometry extended version of postgres), you can take advantage of that using GeoAlchemy, which allows you to define Column types in terms of geometric primitives available in PostGIS. One such data type is Point, which is just what it sounds like.
PostGIS is a bit more difficult to set up than vanilla PostgreSQL, but if you actually intend to do queries based on actual geometric terms, it's well worth the extra (mostly one time) trouble.
Another solution, using plain SQLAlchemy is to define your own column types with the desired semantics, and translate them at compile time to more primitive types supported by your database.
Actually, you could use a property, but not with the builtin property decorator. You'd have to have to work a little harder and create your own, custom descriptor.
You probably want a point class. A decent option is actually to use
a namedtuple, since you don't have to worry about proxying assignment
of individual coordinates. The property gets assigned all or nothing
Point = collections.namedtuple('Point', 'x y')
This would let us at least compare point values. The next step in
writing the descriptor is to work through its methods. There are two methods to think about, __get__
and __set__, and with get, two situations, when called on
an instance, and you should handle actual point values, and when
called on the class, and you should turn it into a column expression.
What to return in that last case is a bit tricky. What we want is something
that will, when compared to a point, returns a column expression that equates
the individual columns with the individual coordinates. well make one more
class for that.
class PointColumnProxy(object):
def __init__(self, x, y):
''' these x and y's are the actual sqlalchemy columns '''
self.x, self.y = x, y
def __eq__(self, pos):
return sqlalchemy.and_(self.x == pos.x,
self.y == pos.y)
All that's left is to define the actual descriptor class.
class PointProperty(object):
def __init__(self, x, y):
''' x and y are the names of the coordinate attributes '''
self.x = x
self.y = y
def __set__(self, instance, value):
assert type(value) == Point
setattr(instance, self.x, value.x)
setattr(instance, self.y, value.y)
def __get__(self, instance, owner):
if instance is not None:
return Point(x=getattr(instance, self.x),
y=getattr(instance, self.y))
else: # called on the Class
return PointColumnProxy(getattr(owner, self.x),
getattr(owner, self.y))
which could be used thusly:
Base = sqlalchemy.ext.declarative.declarative_base()
class MyObject(Base):
x = Column(Float)
y = Column(Float)
pos = PointProperty('x', 'y')
Define your table with a PickleType column type. It will then automatically persist Python objects, as long as they are pickleable. A tuple is pickleable.
mytable = Table("mytable", metadata,
Column('pos', PickleType(),
...
)

Categories

Resources