Python OOP子类重写父类预计算变量的问题与解决
我是面向对象编程(OOP)的新手。我定义了父类Human,其构造函数中的变量bmi赋值为同类方法bod_mass_idx的计算结果(此举因该方法计算耗时,且会被类中后续多个方法重复调用,避免每次使用都重新计算)。
我尝试构造子类MetaHuman,重写父类中存储该耗时计算方法结果的变量,实际场景更复杂,以下是简化示例代码:
class Human: def __init__(self, height, weight): self.height = height self.weight = weight self.prefactor = 1.0 self.bmi = self.bod_mass_idx() def bod_mass_idx(self): return self.prefactor * self.weight/self.height**2 class MetaHuman(Human): def __init__(self, height, weight, fraction): super().__init__(height, weight) self.prefactor = fraction
我希望子类中定义的fraction替换父类的prefactor,但初始化子类后,调用bmi得到的结果未更新:
hum = Human(2,100) m_hum = MetaHuman(2,100,0.5) print(m_hum.bmi) #25.0 print(hum.bmi) #25.0
注:原代码中print(m_hum.bmi())有误,bmi是变量而非方法,已修正
但直接调用方法却能得到预期结果:
print(m_hum.bod_mass_idx()) #12.5 print(hum.bod_mass_idx()) #25.0
请问:
- 为什么对象会出现这种行为?
- 这种情况有什么解决办法?
我的实际场景是父类搭建模拟的基础条件,子类为模拟添加特殊设定,希望通过OOP避免重复代码。
1. 行为原因
创建MetaHuman实例时,执行顺序是先调用super().__init__()(父类构造函数)。在父类构造里,先初始化self.prefactor = 1.0,紧接着调用self.bod_mass_idx()计算出bmi并赋值给self.bmi——此时子类的prefactor还未被赋值,因为子类构造中self.prefactor = fraction是在父类构造执行完之后才运行的。
等父类构造完成,子类才把self.prefactor改成fraction,但bmi已经用最初的prefactor=1.0计算完毕,不会自动重新计算,所以m_hum.bmi仍是25.0。而直接调用bod_mass_idx()时,会使用当前已经更新后的prefactor值,因此能得到正确结果12.5。
2. 解决办法
结合你“避免重复计算耗时方法”的需求,推荐以下几种方案:
方案一:手动更新bmi
在子类构造函数中,更新prefactor后重新计算bmi:
class MetaHuman(Human): def __init__(self, height, weight, fraction): super().__init__(height, weight) self.prefactor = fraction self.bmi = self.bod_mass_idx() # 手动更新bmi
这种方法简单直接,但如果后续prefactor还有修改需求,每次修改后都需要重新计算bmi。
方案二:使用@property实现懒加载+缓存失效
把bmi改为属性,首次访问时计算并缓存,当prefactor变化时清空缓存,确保下次访问bmi会重新计算:
class Human: def __init__(self, height, weight): self.height = height self.weight = weight self._prefactor = 1.0 self._bmi = None # 私有变量存储缓存值 def bod_mass_idx(self): return self._prefactor * self.weight/self.height**2 @property def bmi(self): if self._bmi is None: self._bmi = self.bod_mass_idx() return self._bmi # 给prefactor添加setter,修改时清空缓存 @property def prefactor(self): return self._prefactor @prefactor.setter def prefactor(self, value): self._prefactor = value self._bmi = None # 清空缓存,下次访问bmi会重新计算 class MetaHuman(Human): def __init__(self, height, weight, fraction): super().__init__(height, weight) self.prefactor = fraction
这种方案既避免了重复计算,又能自动响应prefactor的变化,适合prefactor可能被动态修改的场景。
方案三:调整初始化参数传递
如果prefactor在实例创建时就确定,可以直接将子类的fraction传给父类构造,让父类用正确的prefactor计算bmi:
class Human: def __init__(self, height, weight, prefactor=1.0): self.height = height self.weight = weight self.prefactor = prefactor self.bmi = self.bod_mass_idx() def bod_mass_idx(self): return self.prefactor * self.weight/self.height**2 class MetaHuman(Human): def __init__(self, height, weight, fraction): super().__init__(height, weight, fraction) # 直接传入子类的prefactor值
这种方法最简洁,适合prefactor仅在实例初始化时设置的场景,不需要后续修改。
内容的提问来源于stack exchange,提问作者haricash

