I'm trying to implement the following:
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:
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:
Complementary
would fit in this case..complementary()
method (in classes Circle
and Bbox
) that would support the "complementary of the complementary" (a double negation).Thank you for your time.
First of all, you don't want multiple inheritance. A Complementary
region is not both a Circle
and BBox
at the same time! If anything, you should define a class ComplementaryCircle(Circle)
and a second class ComplementaryBBox(BBox)
.
However, given your usage of
# complementary of the circle (whole universe with circ as the "hole") circ_c = Complementary(circ)
what you actually want is to use the decorator pattern. The decorator class is a wrapper, it is neither a subclass of Circle
nor of BBox
, although it does implement the same interface as them. So it's just
class Complementary:
def __init__(self, term):
self.term = term
def move_to(self, xy):
self.term.move_to(xy)
def rotate(self, alpha):
self.term.rotate(alpha)
def is_point_in(self,xy):
return not self.term.is_point_in(xy)
If you want to use abstract classes, introducing one abstract class for that interface would be the way to go. It could even implement that complementary
method:
from abc import ABC, abstractmethod
class Region(ABC):
@abstractmethod
def move_to(self, xy):
"""update the center of mass to point xy"""
pass
@abstractmethod
def rotate(self, alpha):
"""rotate the bounding box (upd the vertices positions)"""
pass
@abstractmethod
def is_point_in(self, xy):
"""checks if xy point belongs to the bounding box"""
return False
def complementary(self):
return Complementary(self)
You'd have class BBox(Region)
and class Circle(Region)
, each implementing those abstract methods. And you'd have your decorator
class Complementary(Region):
def __init__(self, term): # as before
self.term = term
def move_to(self, xy): # as before
self.term.move_to(xy)
def rotate(self, alpha): # as before
self.term.rotate(alpha)
def is_point_in(self,xy): # as before
return not self.term.is_point_in(xy)
def complimentary(self):
return self.term # an optimisation