You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 13:13:00