TL; DR
- I'm trying to implement the complementary of the complementary of a spatial region
- The current solution works with a huge side effect
- The solution is based on multiple inheritance. Which doesn't follow the best practices
- Probably, for my use case, multiple inheritance is not the way to go due to side effects
- I'm seeking helps/suggestions that would point me to another approaches (abstract classes, pattern matching, etc)
In detail
I'm trying to implement the following:
- Consider spatial sets, such as circles and rectangles. And the regions that they occupy are defined by their respective equations
- Consider also the complementary of such sets. Their region is the whole universe except the "hole" defined by the "original" set
- Moreover, the complementary of the complementary is the set itself
- The spatial sets are not static, they can move around (always preserving their shape). In the case of the complementary, this doesn't make much sense and I don't really need this feature for my purposes
I'm treating the spatial sets as classes that have (almost) the same methods: moving and rotating (both of these methods update the attributes); checking if a certain point belongs to the set; etc.
However, my questions arise when trying to implement the complementary. I've tried many different approaches, but unfortunately not even one worked well. And even worse, the implementation that has all the features working, shows strange side effect!
I'll briefly describe what is currently implemented:
- Define different classes (Circle, Rectangle, etc)
- Define class Complementary, which is a child of the classes mentioned above (multiple inheritance), which the only difference between this class and the former ones is the negation of the region occupied by the respective spatial set
Let me share a simplified version of the code:
class Bbox:
def __init__(self,cm,w,h):
self.cm = cm # center of mass
self.w = w # bbox width
self.h = h # bbox height
# more stuff here to initialize
# doesn't matter now
def move_to(self,xy):
# upd center of mass to point xy
self.cm = xy
def rotate(self,alpha):
# rotate the bounding box (upd the vertices positions)
# doesn't matter now
pass
def ispoint(self,xy):
# checks if xy point belongs to the bounding box
# For simplicity sake I'll just override the
# original auxiliary functions of this method
# with the boolean value True
return True
class Circle:
def __init__(self,center,radius):
self.center = center
self.radius = radius
def move_to(self,xy):
# upd center to point xy
self.center = xy
def ispoint(self,xy):
# check if xy point belongs to the circle
xy_isinside = (xy[0]-self.center[0])**2 + (xy[1]- self.center[1])**2 <= self.radius**2
return xy_isinside
class Complementary(Circle,Bbox):
# almost sure that the root cause of my problem
# is the way I'm defining the constructor
# I've tried many alternatives and this
# one works ALMOST fine. More on this later
def __init__(self, term):
self.term = term
# this is not the best way the constructors
# in multiple inheritance are defined
def ispoint(self,xy):
# this is the main thing I want
# I want to negate the region of the term
# whether this term is the "original" one
# or the term is already a complementary
return not self.term.is_point_in(xy)
# NOTE: I cannot use .super() here, because
# it doesn't work in the "second" complementary
# because will always negate the parent's method
## Tests ##
# circle with center (0,0) and radius 1
circ = Circle([0,0],1)
# complementary of the circle (whole universe with circ as the "hole")
circ_c = Complementary(circ)
# complementary of the complementary: equivalent to the circ itself
circ_cc = Complementary(circ_c)
# bounding box with center (0,0) and width=2,height=4
bb = Bbox([0,0],2,4)
# complementary of the bbox
bb_c = Complementary(bb)
# complementary of the complementary: equivalent to the bbox itself
bb_cc = Complementary(bb_c)
# Test .ispoint() method #
print(f"is (0,0) in circ {circ.ispoint([0,0])}") # must be true
print(f"is (0,0) in circ_c {circ_c.ispoint([0,0])}") # must be false
print(f"is (0,0) in circ_cc {circ_cc.ispoint([0,0])};") # must be true
# working as intended
print(f"is (1,2) in bb {bb.ispoint([1,2])}") # must be true
print(f"is (1,2) in bb_c {bb_c.ispoint([1,2])}") # must be false
print(f"is (1,2) in bb_cc {bb_cc.ispoint([1,2])};") # must be true
# working as intended
# NOTE that Python is picking the "correct" .ispoint() method,
# the one respective to the Bbox class
Now, the problem arises when I try to call an attribute of a Complementary object. As an example: try to call circ_c.center, and it throws an error saying that circ_c doesn't have such attribute. However, if first call .move_to() method, then get the attribute, it works just fine:
circ_c.center
circ_c.move_to([0,0])
circ_c.center
I guess python is just dynamically adding new attributes to objects of the class Complementary by initializing them when calling a method that explicitly initialize that same attribute.
Note that .ispoint() uses the center attribute, however .move_to() explicitly initializes center (recall the method definition).
So, long story short, the way I'm using multiple inheritance is causing huge side effects that I don't want. Probably due to the non-conventional way of the __init__ method of the Complementary class (I'm totally aware of that).
I'm seeking any help to my problem. I'm convinced that probably inheritance is not the way to go. Some solutions that are in my head:
- A friend suggested to use pattern matching (a more functional approach), however this suggestion was so vague that I can't wrap my head around it.
- Use Abstract classes, which seems promising to me. However, I'm not clearly seeing where the class
Complementarywould fit in this case. - Somehow use pattern matching with abstract classes
- Somehow implement a
.complementary()method (in classesCircleandBbox) that would support the "complementary of the complementary" (a double negation).
Thank you for your time.