Search code examples
pythoninheritancemethodsinitializationsuperclass

How to initiallize subclass from superclass method?


I read online that the pythonic way of overloading constructors was to create class methods. So I created a RectF class that can be initialized in one of two ways.

class RectF:
    def __init__(self, x: float, y: float, w, h):
        self.x: float = x
        self.y: float = y
        self.width = w
        self.height = h

    @classmethod
    def from_tuples(cls, pos: tuple, size: tuple):
        return cls(pos[0], pos[1], size[0], size[1])

The init constructor takes an argument for each field, while the from_tuples method takes two tuples containing coordinates and size respectively.

However, when I go to initialize an instance of a subclass, using the from_tuples method, an exception is thrown. Using super().__init__() works fine.

class Entity(RectF):
    def __init__(self, pos: tuple, size: tuple, vel: tuple):
        super().__init__(pos[0], pos[1], size[0], size[1])

        # I would like to initialize the superclass using the from_tuples class method.
        # super().from_tuples(pos, size)
        # This throws the following exception: __init__() takes 4 positional arguments but 5 were given

        self.vel_x = vel[0]
        self.vel_y = vel[1]

The code above is an example, and it works fine for now. But for readability and maintainability's sake; and just as a best practice, it would be useful to initialize objects using the least amount of arguments, especially if they get more complex over time.


Solution

  • By the time __init__ gets called, the object has already been constructed, so it's too late to use from_tuples.

    Don't use the number of arguments as the measure of simplicity. Instead, think in terms of which methods can be used to implement others. If you want tuples to be the fundamental building block of a rectangle, you can do that:

    class RectF:
        def __init__(self, pos: tuple, size: tuple):
            self.x: float = pos[0]
            self.y: float = pos[1]
            self.width = size[0]
            self.height = size[1]
    
        # No good name for this method comes to mind
        @classmethod
        def from_separate_values(cls, x, y, w, h):
            return cls((x, y), (w, h))
    
    
    class Entity(RectF):
        def __init__(self, pos: tuple, size: tuple, vel: tuple):
            super().__init__(pos, size)
            self.vel_x = vel[0]
            self.vel_y = vel[1]
    
        @classmethod
        def from_separate_values(cls, x, y, w, h, vx, vy):
            rv = super().from_separate_values(x, y, w, h)
            rv.vel_x = vx
            rv.vel_y = vy
            return rv