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.

Answer

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)