__repr__比__str__更适合调试,因为它面向开发者,强调精确、无歧义和可复现性:理想情况下eval(repr(obj))能重建对象;必须包含类名与关键字段(如point(x=3, y=-1)),避免省略类型或细节;需规避耗时操作与递归崩溃,并在日志和测试中直接决定排查效率。

为什么 __repr__ 比 __str__ 更适合调试
调试时你真正需要的不是“好看”,而是“能一眼看出对象状态是否符合预期”。__repr__ 的设计目标就是可逆性与明确性:理想情况下,eval(repr(obj)) 应该能重建对象(虽不强制,但这是约定)。而 __str__ 是给人看的,常省略类型、字段名甚至关键细节,比如 str(datetime.now()) 输出 '2024-06-12 15:23:45.123456',但你不知道它是不是 datetime 类型、有没有 tzinfo。
-
__repr__应包含类名、关键字段名和值,例如Point(x=3, y=-1),而不是(3, -1) - 避免在
__repr__中调用耗时操作(如数据库查询、文件读取),否则print()或调试器展开时会卡住 - 如果字段值本身可能触发递归(如循环引用),必须手动截断或标记,否则
repr()会抛出RecursionError
如何写一个安全、信息充分的 __repr__
核心是控制输出长度和结构,优先暴露区分实例的关键字段。别堆砌所有属性,只留调试真正需要的——通常是构造参数或影响行为的字段。
- 用
f"{self.__class__.__name__}({', '.join(...)})"模板,保证类名可见 - 对字符串字段用
repr()包裹(如repr(self.name)),避免换行符、引号混淆视觉 - 数值、布尔、None 可直接拼接;嵌套对象调用其
repr(),但需检查是否可能递归(例如自引用链表节点) - 示例:
def __repr__(self): return f"{self.__class__.__name__}(id={self.id}, name={repr(self.name)}, active={self.active})"
遇到 RecursionError: maximum recursion depth exceeded 怎么办
这是实现 __repr__ 时最典型的崩溃点,尤其在树、图、ORM 模型或带反向引用的数据结构中。Python 默认递归深度约 1000 层,而一个深度为 5 的嵌套对象就可能触发。
- 不要依赖
sys.getrecursionlimit()调高限制——这治标不治本,且掩盖设计问题 - 在
__repr__开头加一个轻量级守卫:用getattr(self, '_repr_running', False)标记是否已在递归中,若为真则返回占位符如'<...>'</...> - 更稳妥的做法是显式限制嵌套层级,比如传入
_depth=0参数并递增,超过 3 层就缩写子对象为'[...]' - 对于 SQLAlchemy 或 Pydantic 模型,优先复用其内置
__repr__(它们已处理循环),或用model_dump()+ 白名单字段构造简洁输出
__repr__ 和日志、单元测试的关系
很多人忽略:日志记录器(如 logging.debug("obj=%r", obj))默认调用 repr();pytest 在断言失败时也依赖 __repr__ 显示期望/实际值。这意味着你的 __repr__ 直接影响错误排查效率。
- 确保输出不含不可见字符(如
\x00)、超长二进制数据或非 UTF-8 字节串,否则日志可能乱码或截断 - 单元测试里若用
assert str(obj) == "expected",其实是绕过了你精心写的__repr__—— 应该用assert repr(obj) == "expected_repr"来验证 - 如果对象含敏感字段(如密码、token),
__repr__必须脱敏(显示token='***'),否则日志泄露风险极高
写 __repr__ 不是炫技,是给未来的自己留线索。字段选哪些、怎么格式化、要不要脱敏——每个决定都在回答一个问题:“当我凌晨三点盯着 pdb 里的这个对象时,我最需要知道什么?”
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











