继承fractions.Fraction时子类运算符返回父类对象的问题
继承fractions.Fraction时运算符返回父类对象的解决方案与OOP原则探讨
当继承Python的fractions.Fraction类时,子类的数学运算符会返回父类Fraction对象而非子类对象,导致无法保留子类的自定义行为。例如:
from fractions import Fraction class OtherFraction(Fraction): def __repr__(self): return f'OtherFraction({self.numerator}, {self.denominator})' a = OtherFraction(1, 2) b = OtherFraction(1, 3) c = a + b print(repr(a), repr(b), repr(c))
运行输出:
OtherFraction(1, 2) OtherFraction(1, 3) Fraction(5, 6)
无需重写所有运算符的解决方法
不用逐个手动重写运算符方法,可以通过动态生成运算符方法的方式批量处理:
from fractions import Fraction class OtherFraction(Fraction): def __repr__(self): return f'OtherFraction({self.numerator}, {self.denominator})' # 定义需要处理的算术运算符列表 operator_names = [ '__add__', '__sub__', '__mul__', '__truediv__', '__floordiv__', '__mod__', '__pow__', '__radd__', '__rsub__', '__rmul__', '__rtruediv__', '__rfloordiv__', '__rmod__', '__rpow__' ] def wrap_operator(original_method): def wrapped(self, other): # 调用父类的运算符方法得到结果 result = original_method(self, other) # 如果结果是Fraction实例,转换为子类实例返回 if isinstance(result, Fraction): return OtherFraction(result.numerator, result.denominator) return result return wrapped # 批量为子类绑定包装后的运算符方法 for name in operator_names: original = getattr(Fraction, name) setattr(OtherFraction, name, wrap_operator(original))
测试后,a + b会返回OtherFraction(5, 6),符合预期。这种方式通过包装父类的运算符方法,自动将返回的Fraction实例转换为子类实例,避免了重复代码。
是否违背面向对象编程原则
这种期望子类运算符返回子类对象的想法不违背OOP原则:
- 符合协变返回类型的设计思路:子类方法返回子类实例,仍然满足父类方法的返回类型约定(子类是父类的子类型,子类实例可以被安全地当作父类实例使用)。
- 符合里氏替换原则:子类实例可以替换父类实例参与运算,同时保留自身的自定义行为,不会破坏原有代码的正确性。
- 实际场景中,当子类扩展了父类的行为(比如自定义
__repr__、额外属性或方法),要求运算结果保持子类类型是合理需求,能确保子类的特性被保留。
需要注意:如果子类添加了父类没有的额外属性,直接转换时需要确保这些属性被正确传递,否则会导致属性丢失。但在仅重写方法(如示例中的__repr__)的场景下,这种方法完全适用。
内容的提问来源于stack exchange,提问作者Saul Axel Martinez Ortiz
相关产品推荐
相关产品推荐

