repr 应返回可被 eval 执行或清晰还原对象构造的字符串,优先用关键字参数格式展示关键属性;str 侧重人类可读性,二者在容器中递归调用规则不同,缺失 repr 会严重损害调试效率。

repr 应该返回可被 eval 执行的字符串
这是 repr 最核心的设计意图:生成一个“能还原出原对象”的字符串表示,理想情况下可以直接喂给 eval() 重建对象。比如 repr("hello") 返回 '"hello"'(带引号),而 str("hello") 只返回 hello(不带引号)。
实际开发中容易忽略这点,导致自定义类的 __repr__ 返回了无上下文的描述(如 "<user object>"</user>),调试时完全看不出字段值。
- 优先包含关键属性,用关键字参数风格:
User(name="alice", age=30) - 避免省略、模糊表述(如
"User(…)"或"User instance") - 若对象不可安全
eval(比如含函数、文件句柄),至少保证字段名+值清晰可读,不加引号反而容易误判类型
str 是给人看的,repr 是给程序员/系统看的
str 的目标是可读性与语义友好,比如 datetime 对象的 str() 输出 "2024-05-22 14:30:00";而 repr() 会带上模块路径和完整构造参数:datetime.datetime(2024, 5, 22, 14, 30, 0)。
这个分工一旦颠倒,就会在日志、调试器或 print() 中造成困惑——比如把 repr 当成用户界面输出,结果看到一串带引号和转义的字符串。
立即学习“Python免费学习笔记(深入)”;
-
str可以省略技术细节(如编码、内存地址),但必须保持语义准确 -
repr不必考虑换行或长度,宁长勿缺;str可适当截断或格式化(如Path对象的str()返回纯路径字符串,不带PosixPath(...)包装) - 交互式环境(如 IPython)默认调用
repr显示结果,不是str—— 这是很多人误以为 “print 和直接敲变量效果一样” 的根源
自定义类里漏写 __repr__ 就等于放弃调试效率
如果只实现 __str__ 而没写 __repr__,Python 会 fallback 到默认的 <__main__.myclass object at></__main__.myclass>,所有字段信息彻底丢失。这时候 logging.debug("user: %r", user) 或 pprint([user1, user2]) 都毫无意义。
更隐蔽的问题是:某些库(如 dataclasses、attrs)默认生成的 __repr__ 虽然可用,但可能包含你不希望暴露的字段(如密码哈希、内部缓存),需要显式覆盖。
- 哪怕只是临时调试,也建议第一行就补上
def __repr__(self): return f"{self.__class__.__name__}({self.__dict__})" - 生产代码中,用
dataclass(repr=True)是安全起点,但记得检查字段是否都该暴露 - 避免在
__repr__里触发副作用(如访问数据库、计算耗时属性),它可能被日志框架、IDE 变量面板等非预期调用
repr 和 str 在容器里的行为差异常被低估
列表、字典等容器在调用 repr 时,会递归使用元素的 repr;而 str 容器则调用元素的 str。这意味着:如果你的自定义类只有 __str__,放进 list 后用 print(my_list) 看到的是友好的字符串,但用 logging.info("%r", my_list) 就又变回 <myclass object at ...></myclass>。
这种不一致在序列化、日志采样、单元测试断言里特别容易踩坑——比如你 assert str(obj) == "ok" 通过了,但 assert repr(obj) == "ok" 必然失败,因为类型根本不匹配。
- 容器的
__str__和__repr__行为由其自身实现决定,不继承元素逻辑,但内容渲染依赖元素的对应方法 - 测试时若用
%r格式化日志或断言,务必确保所有嵌套对象都有合理__repr__ - JSON、YAML 等序列化不走
str/repr,它们有自己的转换协议,别指望重载这两个方法就能控制导出格式
最常被跳过的其实是 repr 的“可逆性”要求:它不一定要真能 eval,但得让开发者一眼看出“这东西是怎么造出来的”。少一个字段、多一个空格、漏掉 None 或空字符串的显式表示,都可能让排查时间翻倍。










