Search code examples
pythoninheritanceclass-method

Use inherited class method within __init__


I have a parent class that is inherited by several children. I would like to initialize one of the children using the parent's @classmethod initializers. How can I do this? I tried:

class Point(object):
    def __init__(self,x,y):
        self.x = x
        self.y = y

    @classmethod
    def from_mag_angle(cls,mag,angle):
        x = mag*cos(angle)
        y = mag*sin(angle)
        return cls(x=x,y=y)


class PointOnUnitCircle(Point):
    def __init__(self,angle):
        Point.from_mag_angle(mag=1,angle=angle)


p1 = Point(1,2)
p2 = Point.from_mag_angle(2,pi/2)
p3 = PointOnUnitCircle(pi/4)
p3.x #fail

Solution

  • If you try to write __init__ like that, your PointOnUnitCircle has a different interface to Point (as it takes angle rather than x, y) and therefore shouldn't really be a sub-class of it. How about something like:

    class PointOnUnitCircle(Point):
    
        def __init__(self, x, y):
            if not self._on_unit_circle(x, y):
                raise ValueError('({}, {}) not on unit circle'.format(x, y))
            super(PointOnUnitCircle, self).__init__(x, y)
    
        @staticmethod
        def _on_unit_circle(x, y):
            """Whether the point x, y lies on the unit circle."""
            raise NotImplementedError
    
        @classmethod
        def from_angle(cls, angle):
            return cls.from_mag_angle(1, angle)
    
        @classmethod
        def from_mag_angle(cls, mag, angle):  
            # note that switching these parameters would allow a default mag=1
            if mag != 1:
                raise ValueError('magnitude must be 1 for unit circle')
            return super(PointOnUnitCircle, cls).from_mag_angle(1, angle)
    

    This keeps the interface the same, adds logic for checking the inputs to the subclass (once you've written it!) and provides a new class method to easily construct a new PointOnUnitCircle from an angle. Rather than

    p3 = PointOnUnitCircle(pi/4)
    

    you have to write

    p3 = PointOnUnitCircle.from_angle(pi/4)