Python中为内置float类重写__str__和__repr__魔术方法时的递归与输出异常问题解析
Python中为内置float类重写__str__和__repr__魔术方法时的递归与输出异常问题解析
嘿,我来帮你拆解这里面的核心逻辑,其实问题出在内置类型(比如float)的方法是C实现的,和纯Python类的方法调用逻辑有差异,再加上你重写__str__和__repr__后形成的调用链,才导致了这些奇怪的输出和递归错误。咱们逐个分析你的四个测试场景:
先明确基础前提
对于原生float,str(float(512))和repr(float(512))输出确实都是'512.0',但这只是结果相同,底层调用逻辑在子类继承时会完全不一样——尤其是当你重写了这两个魔术方法后,内置类型的父类方法可能会“绕过”预期的逻辑,转而调用子类的其他方法。
Case 1a(正确输出的场景)
你的代码是:
def __str__(self): return f"{super().__repr__()} {self.unit}" # 1. def __repr__(self): return f'Storage({super().__repr__()}, "{self.unit}")' # a.
- 当调用
str(storage)时:执行子类的__str__,里面直接调用父类float的__repr__——这个方法是C实现的,会直接读取实例的数值部分,返回'512.0',拼接单位后得到'512.0 GB',完全符合预期。 - 当调用
repr(storage)时:执行子类的__repr__,同样调用父类float的__repr__获取数值字符串,拼接成类实例的标准表示'Storage(512.0, "GB")',也没问题。
这个场景能work的核心是:你直接调用了父类的__repr__,它不会触发子类的任何重写方法,直接返回原始数值的字符串。
Case 1b(repr触发递归错误)
代码:
def __str__(self): return f"{super().__repr__()} {self.unit}" # 1. def __repr__(self): return f'Storage({super().__str__()}, "{self.unit}")' # b.
str(storage)依然正常:和Case1a一样,用父类__repr__获取数值,不受影响。repr(storage)触发RecursionError:原因是父类float的__str__方法在处理子类实例时,会调用子类的__repr__——这是因为C实现的内置方法无法直接处理子类的额外属性(比如你的unit),所以退而求其次调用__repr__。这样就形成了循环:repr(storage)→ 子类__repr__调用super().__str__()→ 父类__str__调用子类__repr__→ 再次进入子类__repr__……无限循环直到栈溢出。
Case 2a(str输出异常)
代码:
def __str__(self): return f"{super().__str__()} {self.unit}" # 2. def __repr__(self): return f'Storage({super().__repr__()}, "{self.unit}")' # a.
repr(storage)正常:和Case1a一样,用父类__repr__获取数值,拼接成正确的实例表示。str(storage)输出奇怪的'Storage(512.0, "GB") GB':原因和Case1b类似,父类float的__str__调用了子类的__repr__。当你在子类__str__里调用super().__str__()时,父类方法无法直接返回子类实例的数值字符串,转而调用你重写的__repr__,得到'Storage(512.0, "GB")',再拼接单位就变成了那个奇怪的结果。
Case 2b(str和repr都触发递归)
代码:
def __str__(self): return f"{super().__str__()} {self.unit}" # 2. def __repr__(self): return f'Storage({super().__str__()}, "{self.unit}")' # b.
这个场景是双重递归:
- 调用
str(storage)时:子类__str__调用super().__str__()→ 父类__str__调用子类__str__(因为子类有__str__方法,C实现的父类方法会触发它),形成无限循环:str→子类__str__→父类__str__→子类__str__…… - 调用
repr(storage)时:子类__repr__调用super().__str__()→ 父类__str__调用子类__str__→ 子类__str__又调用super().__str__(),同样进入无限循环,最终都触发RecursionError。
总结一下避坑要点
当子类化内置类型(如float、int、str)时,尽量避免在重写的__str__/__repr__中调用父类的__str__,而是优先调用父类的__repr__来获取原始数值的字符串——因为父类的__repr__对于内置类型来说,会直接返回最原始的、不依赖子类方法的数值表示,不会触发递归或意外调用子类的其他方法。
备注:内容来源于stack exchange,提问作者Andras Vanyolos
相关产品推荐
相关产品推荐

