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
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)
You can override the subclass's __new__
method to construct instances from the superclass's alternate constructor as shown below.
import math
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
@classmethod
def from_polar(cls, radius, angle):
x = radius * math.cos(angle)
y = radius * math.sin(angle)
return cls(x, y)
class PointOnUnitCircle(Point):
def __new__(cls, angle):
point = Point.from_polar(1, angle)
point.__class__ = cls
return point
def __init__(self, angle):
pass
Note that in __new__
, the line point = Point.from_polar(1, angle)
cannot be replaced by point = super().from_polar(1, angle)
because whereas Point
sends itself as the first argument of the alternate constructor, super()
sends the subclass PointOnUnitCircle
to the alternate constructor, which circularly calls the subclass's __new__
that calls it, and so on until a RecursionError
occurs. Also note that even though __init__
is empty in the subclass, without overriding __init__
in the subclass, the superclass's __init__
would automatically be called immediately after __new__
, undoing the alternate constructor.
Alternatively, some object designs are simpler with composition than with inheritance. For example, you could replace the above PointOnUnitCircle
class without overriding __new__
with the following class.
class UnitCircle:
def __init__(self, angle):
self.set_point(angle)
def set_point(self, angle):
self.point = Point.from_polar(1, angle)