子类类方法调用父类类方法的正确方式及super函数误区解析
我有一个父类Rectangle,支持两种初始化方式:标准的__init__方法,以及类方法with_vectors通过正交向量初始化。子类Square继承自Rectangle,我希望它也能实现类似的双初始化结构,并且复用父类的with_vectors逻辑,但使用super()调用时出现错误,只有直接调用Rectangle.with_vectors能运行,但不确定这种方式是否正确。
示例代码:
import numpy as np def scalar_product(vector_a: tuple, vector_b: tuple): if len(vector_a) != len(vector_b): raise ValueError(f"The vectors provided do not have the same dimensionality.") return (np.array(vector_a) * np.array(vector_b)).sum() class Rectangle: def __init__(self, side: float, width: float): self.side = side self.width = width @classmethod def with_vectors(cls, vector_a: tuple, vector_b: tuple): if scalar_product(vector_a, vector_b) != 0: raise ValueError(f"The vectors must be orthogonal") side = np.sqrt((np.array(vector_a) ** 2).sum()) width = np.sqrt((np.array(vector_b) ** 2).sum()) return cls(side, width) def get_area(self): return self.side * self.width class Square(Rectangle): def __init__(self, side: float): super().__init__(side, side) @classmethod def with_vectors(cls, vector_a: tuple): vector_b = (vector_a[1], -vector_a[0]) # return super().with_vectors(vector_a, vector_b) # TypeError: Square.__init__() takes 2 positional arguments but 3 were given # return super(Rectangle, cls).with_vectors(vector_a, vector_b) # AttributeError: 'super' object has no attribute 'with_vectors' return Rectangle.with_vectors(vector_a, vector_b) # 能运行但返回Rectangle实例
一、你对super()的误解点
第一个错误的原因:
当调用super().with_vectors(vector_a, vector_b)时,父类的with_vectors方法中会执行cls(side, width),这里的cls是当前子类Square(类方法的cls参数会传递调用该方法的类)。而Square的__init__只接受1个位置参数,传入side和width两个参数自然触发TypeError。第二个错误的原因:
super(Rectangle, cls)的作用是跳过Rectangle类,直接查找Rectangle的父类(即object),而object类并没有with_vectors方法,因此抛出AttributeError。
二、当前直接调用方式的问题
你现在用Rectangle.with_vectors(vector_a, vector_b)确实能运行,但它返回的是Rectangle实例,而不是Square实例,这不符合子类Square的设计预期——用户调用Square.with_vectors应该得到正方形对象,而不是矩形对象。
三、更合适的实现方案
方案1:兼容父类调用的子类构造方法
修改Square的__init__,让它支持可选的width参数,这样父类with_vectors中的cls(side, width)就能正常调用,同时保证正方形的边长一致:
class Square(Rectangle): def __init__(self, side: float, width: float = None): # 强制width与side保持一致,确保是正方形 actual_width = side if width is None else side super().__init__(side, actual_width) @classmethod def with_vectors(cls, vector_a: tuple): vector_b = (vector_a[1], -vector_a[0]) return super().with_vectors(vector_a, vector_b)
此时调用Square.with_vectors会返回Square实例,同时复用了父类的向量验证和边长计算逻辑。
方案2:拆分父类逻辑,复用核心计算
把父类with_vectors中的验证和边长计算逻辑拆成静态方法,子类直接复用核心逻辑,自己创建实例:
class Rectangle: def __init__(self, side: float, width: float): self.side = side self.width = width @staticmethod def _validate_and_calculate_sides(vector_a: tuple, vector_b: tuple): if scalar_product(vector_a, vector_b) != 0: raise ValueError(f"The vectors must be orthogonal") side = np.sqrt((np.array(vector_a) ** 2).sum()) width = np.sqrt((np.array(vector_b) ** 2).sum()) return side, width @classmethod def with_vectors(cls, vector_a: tuple, vector_b: tuple): side, width = cls._validate_and_calculate_sides(vector_a, vector_b) return cls(side, width) def get_area(self): return self.side * self.width class Square(Rectangle): def __init__(self, side: float): super().__init__(side, side) @classmethod def with_vectors(cls, vector_a: tuple): vector_b = (vector_a[1], -vector_a[0]) side, _ = cls._validate_and_calculate_sides(vector_a, vector_b) # 正方形边长相等,直接取一个值即可 return cls(side)
这种方式更灵活,既复用了父类的验证逻辑,又严格遵循了Square的构造规则,返回的是纯正的Square实例。
内容的提问来源于stack exchange,提问作者Perceval Desforges

