日志中__str__和__repr__输出异常,根本原因是用途错配:__str__应提供无引号、易读的简洁描述(如user(alice, 30)),专用于日志;__repr__须精确可复现(如user(name='alice', age=30)),仅用于调试。日志应统一用%s触发__str__,禁用f-string拼接,避免提前执行或引号污染。

为什么 __str__ 和 __repr__ 日志里总输出得不对
日志里打印对象时只看到 <__main__.user object at></__main__.user>,不是因为没写这两个方法,而是写了但没区分用途:把 __str__ 当成调试用、把 __repr__ 塞进日志格式化字符串里,结果要么信息太少,要么带引号和转义符干扰日志可读性。
关键判断:日志输出应优先依赖 __str__(面向用户/运维的简洁描述),而 __repr__ 必须能“被 eval 还原”或至少明确标识类型+关键字段——否则在 pdb 或日志上下文里查问题会多绕三步。
__str__ 怎么写才适合日志场景
它应该返回无格式、无引号、易扫读的纯文本,不暴露内部实现细节,也不包含换行或控制字符。日志系统(如 logging.info("user: %s", user))默认调用的就是这个。
- 用
str()而非repr()包裹字段值,避免多余引号:f"User({self.name}, {self.age})"→User(Alice, 30);若写成f"User({repr(self.name)}, {self.age})"会变成User('Alice', 30) - 敏感字段(如密码哈希)必须脱敏,哪怕
__repr__里保留完整值 - 避免耗时操作:不要在
__str__里查数据库、调 API 或格式化大列表 - 如果对象状态可能为
None或未初始化,先做空值处理,否则日志里抛AttributeError
__repr__ 的正确姿势是能“一眼定位问题”
它不是给日志用的,是给开发者看的——所以要精确、可复制、带类型提示。Python 内置容器(list、dict)的 __repr__ 就是范本:字段名 + 值 + 类型边界清晰。
- 格式建议统一为
f"{self.__class__.__name__}({arg1!r}, {arg2!r}, ...)",其中!r确保字段本身也走repr,比如字符串带引号、None显示为None - 只暴露诊断必需字段:ID、状态码、关键标识符,别塞整个嵌套字典
- 如果对象有动态计算属性(如
is_valid),__repr__里不要调用它——可能触发副作用或异常 - 注意循环引用:若对象含 self-reference(如树节点的
parent),__repr__必须加保护逻辑,否则logging.debug(obj)直接栈溢出
日志配置里怎么避免踩坑
很多团队直接在 logging 格式里写 %(message)s,却忘了 logger 传参时是否已触发 __str__。最常错的是用 logging.debug("user=%s", user)(正确) vs logging.debug(f"user={user}")(危险)。
- 用百分号格式化或
logging.debug("user=%s", user):安全,延迟调用__str__ - 避免 f-string 拼接对象:
f"user={user}"会在日志级别被过滤时仍执行__str__,浪费性能且可能抛异常 - 如果必须在格式串里用
__repr__(比如排查时想看原始结构),显式调用:logging.debug("raw: %r", user),而非依赖默认行为 - 自定义 logging.Handler 中若重写了
format(),检查是否误用了str(record.msg)而非record.getMessage()—— 后者才保证参数化格式化逻辑生效
真正容易被忽略的是:当对象嵌套层级深(比如 User 里有 Profile,Profile 里有 Address),每个类都得独立实现合理的 __str__ 和 __repr__,缺一层就可能让整条日志变成一串内存地址。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











