__str__ 应返回用户友好的可读字符串(如 "alice (alice@example.com)"),而 __repr__ 必须返回开发者可用的唯一标识字符串(如 "user(id=1, name='alice', email='alice@example.com')"),二者需独立实现、不可互调,且容器打印默认使用 __repr__。

__str__ 应该返回对用户友好的字符串
当你调用 print(obj) 或 str(obj) 时,Python 会找 __str__。它的目标是“让人读得懂”,比如显示商品名、时间简写、状态摘要等,不追求唯一性或可还原性。
常见错误是直接返回 repr 的内容,或者塞进太多调试信息(如内存地址、字段 ID),反而让用户困惑。
- 避免包含内部属性名或下划线前缀字段,除非它们本身就是用户概念(如
_balance是银行 App 的核心字段) - 不要返回空字符串或纯类型名(如
"<user object>"</user>),这等于没实现 - 如果对象状态复杂,优先用自然语言短语,而不是结构化 JSON 格式(那是
__repr__的事)
示例:
def __str__(self):<br> return f"{self.name} ({self.email})"
__repr__ 必须能“重建对象”或至少唯一标识
__repr__ 是给开发者看的,触发场景包括交互式解释器中直接输入变量名、logging.debug()、断点调试时变量检查。它应该尽可能做到:一眼看出类型、关键字段、实例差异。
最常踩的坑是只返回类名("User()"),导致多个实例在日志里全长得一样;或者漏掉关键字段(比如没包含 id),无法定位具体对象。
- 格式推荐:
"{class_name}({arg1!r}, {arg2!r}, ...)"—— 用!r确保字符串带引号、转义符可见 - 如果对象不可重建(比如含文件句柄、网络连接),至少保证输出含唯一标识(如
id=12345或uuid='a1b2c3...') - 避免调用可能抛异常或耗时的方法(如数据库查询),
__repr__必须快且安全
示例:
def __repr__(self):<br> return f"User(id={self.id!r}, name={self.name!r}, email={self.email!r})"
两者行为不一致时,repr 不能依赖 str 的结果
有些新手会这样写:def __repr__(self): return str(self)。这看似省事,但破坏了 __repr__ 的设计契约:一旦 __str__ 被改成多行、带 emoji 或本地化文本,__repr__ 就彻底失去调试价值。
更隐蔽的问题是:当 __str__ 内部逻辑出错(比如访问了 None 属性),__repr__ 也会跟着挂,导致连 debug 都没法做。
- 两个方法必须独立实现,互不调用
- 测试时可以加断言:
assert "User(" in repr(obj),确保__repr__至少含类型标识 - 如果字段多,考虑用
dataclasses自动生成__repr__(它默认符合规范),再单独定制__str__
在容器里打印时,Python 默认用 repr 而非 str
这是最容易被忽略的一点:当你把对象放进 list、dict 或 set 后打印整个容器,Python 对每个元素调用的是 __repr__,不是 __str__。所以即使你精心写了友好的 __str__,用户在 REPL 里看到 [User(id=1, name='Alice'), User(id=2, name='Bob')] 还是会觉得“太程序员”。
这不是 bug,是设计使然——容器内容需要明确性,而非可读性。
- 别指望用户通过
print([u1, u2])理解业务含义;该用循环 +str(u)显式展示 - 日志中记录列表时,如果想更友好,手动映射:
logger.info("users: %s", [str(u) for u in users]) - IPython/Jupyter 中可定义
_repr_pretty_方法来优化容器内显示,但标准 Python 不支持
真正难的不是写对这两个方法,而是每次新增字段时,记得同步更新 __repr__ 里的字段列表——少一个 id,就可能让线上问题排查慢十分钟。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











