从打印对象到高质量调试:彻底理解 Python 中 __repr__ 和 __str__ 的区别
在 Python 编程中,很多人第一次接触类时,都会遇到这样一个小尴尬:
python
class User:
def __init__(self, name, age):
self.name = name
self.age = age
user = User("Alice", 18)
print(user)
输出结果可能是:
python
<__main__.User object at 0x104b2e550>
这是什么意思?它当然不是错,但它对我们几乎没有帮助。我们真正想看到的可能是:
python
User(name='Alice', age=18)
或者更友好一点:
python
用户 Alice,18 岁
这背后就涉及 Python 中两个非常重要的魔术方法:
python
__repr__
__str__
它们都和"对象如何变成字符串"有关,但用途完全不同。简单说:
__repr__面向开发者,强调准确、明确、可调试。
__str__面向用户,强调友好、可读、易理解。
这篇文章会从基础语法讲到真实项目实践,帮助你真正理解 __repr__ 和 __str__ 的区别,并掌握 Python最佳实践 中对象字符串表示的设计方法。
一、为什么对象需要字符串表示?
Python 从诞生之初就强调简洁、可读和表达力。它既可以写自动化脚本,也能支撑 Web 后端、数据科学、人工智能、运维工具和大型工程系统。
在这些场景里,调试和日志非常重要。
比如你在开发一个订单系统,日志里出现:
python
<__main__.Order object at 0x103c97d60>
你几乎无法知道这个订单是谁的、多少钱、什么状态。
但如果日志是这样:
python
Order(id='A001', amount=199.0, status='paid')
排查问题的效率会高很多。
这就是 __repr__ 和 __str__ 的价值:它们让对象在打印、日志、调试、交互式环境中呈现出更有意义的信息。
二、先看结论:__repr__ 和 __str__ 的核心区别
可以先记住一句话:
text
__repr__:给开发者看的,目标是明确、无歧义、最好能复现对象。
__str__ :给用户看的,目标是友好、自然、适合展示。
举个例子:
python
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def __repr__(self):
return f"Product(name={self.name!r}, price={self.price!r})"
def __str__(self):
return f"{self.name},售价 ¥{self.price:.2f}"
p = Product("Python 实战手册", 89)
print(repr(p))
print(str(p))
print(p)
输出:
python
Product(name='Python 实战手册', price=89)
Python 实战手册,售价 ¥89.00
Python 实战手册,售价 ¥89.00
注意最后一行:
python
print(p)
默认调用的是:
python
str(p)
所以它会优先使用 __str__。
三、__repr__ 是什么?
__repr__ 的全称可以理解为 representation,即"表示形式"。
它的目标是:尽可能准确地表达这个对象是什么。
例如:
python
class User:
def __init__(self, username, role):
self.username = username
self.role = role
def __repr__(self):
return f"User(username={self.username!r}, role={self.role!r})"
user = User("alice", "admin")
print(repr(user))
输出:
python
User(username='alice', role='admin')
为什么这里用了 !r?
python
f"{self.username!r}"
等价于:
python
repr(self.username)
它会保留字符串的引号,让结果更准确。
对比一下:
python
name = "Alice"
print(f"{name}")
print(f"{name!r}")
输出:
python
Alice
'Alice'
在 __repr__ 中,使用 !r 是非常推荐的做法,因为它能减少歧义。
例如:
python
User(username=Alice, role=admin)
就不如:
python
User(username='Alice', role='admin')
清楚。
四、__str__ 是什么?
__str__ 的目标是:给人看的友好字符串。
比如:
python
class User:
def __init__(self, username, role):
self.username = username
self.role = role
def __str__(self):
return f"{self.username}({self.role})"
user = User("alice", "admin")
print(str(user))
print(user)
输出:
python
alice(admin)
alice(admin)
__str__ 常用于:
text
命令行输出
用户界面展示
报表文本
错误提示
业务描述
例如订单对象:
python
class Order:
def __init__(self, order_id, amount, status):
self.order_id = order_id
self.amount = amount
self.status = status
def __str__(self):
return f"订单 {self.order_id}:¥{self.amount:.2f},状态:{self.status}"
order = Order("A001", 199.9, "已支付")
print(order)
输出:
python
订单 A001:¥199.90,状态:已支付
这就很适合给业务人员、用户或运营同学看。
五、当只定义一个时,会发生什么?
这是理解二者关系的关键。
1. 只定义 __repr__
python
class User:
def __init__(self, name):
self.name = name
def __repr__(self):
return f"User(name={self.name!r})"
u = User("Alice")
print(repr(u))
print(str(u))
print(u)
输出:
python
User(name='Alice')
User(name='Alice')
User(name='Alice')
如果没有定义 __str__,Python 会退而求其次使用 __repr__。
所以在实际开发中,如果你只想写一个,优先写 __repr__。
2. 只定义 __str__
python
class User:
def __init__(self, name):
self.name = name
def __str__(self):
return f"用户:{self.name}"
u = User("Alice")
print(str(u))
print(repr(u))
输出类似:
python
用户:Alice
<__main__.User object at 0x104b2e550>
这时 str(u) 友好了,但 repr(u) 仍然是默认内存地址形式,不利于调试。
这也是为什么我建议业务核心对象优先实现 __repr__。
六、交互式环境为什么更偏爱 __repr__?
在 Python REPL 或 Jupyter Notebook 中,直接输入一个对象:
python
u
显示结果通常使用 repr(u),而不是 str(u)。
原因很简单:交互式环境主要面向开发者,它更关心对象的准确表达。
python
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point(x={self.x!r}, y={self.y!r})"
def __str__(self):
return f"({self.x}, {self.y})"
p = Point(3, 4)
print(str(p))
print(repr(p))
输出:
python
(3, 4)
Point(x=3, y=4)
在调试中,你通常更想看到:
python
Point(x=3, y=4)
因为它告诉你对象类型和字段含义。
七、列表、字典等容器中使用的是 __repr__
这是很多人容易忽略的细节。
看下面的代码:
python
class User:
def __init__(self, name):
self.name = name
def __str__(self):
return f"用户:{self.name}"
users = [User("Alice"), User("Bob")]
print(users)
输出可能是:
python
[<__main__.User object at 0x...>, <__main__.User object at 0x...>]
为什么没有使用 __str__?
因为列表在显示内部元素时,调用的是元素的 repr(),不是 str()。
如果想让列表打印更清晰,需要实现 __repr__:
python
class User:
def __init__(self, name):
self.name = name
def __repr__(self):
return f"User(name={self.name!r})"
def __str__(self):
return f"用户:{self.name}"
users = [User("Alice"), User("Bob")]
print(users)
输出:
python
[User(name='Alice'), User(name='Bob')]
这在 Python实战 中非常重要。日志里经常打印列表、字典、集合,如果对象没有好的 __repr__,日志质量会很差。
八、最佳实践:__repr__ 最好能帮助复现对象
理想情况下,__repr__ 返回的字符串应该尽量像一段可以重新创建对象的代码。
比如:
python
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x!r}, {self.y!r})"
p = Point(1, 2)
print(repr(p))
输出:
python
Point(1, 2)
这个结果非常清晰,甚至可以直接用于重新构造对象。
当然,真实项目里不一定每个对象都能完全复现,比如数据库连接、文件句柄、网络请求对象等。但至少应该包含关键字段。
例如:
python
class DatabaseConnection:
def __init__(self, host, port, database):
self.host = host
self.port = port
self.database = database
def __repr__(self):
return (
f"DatabaseConnection("
f"host={self.host!r}, "
f"port={self.port!r}, "
f"database={self.database!r})"
)
如果涉及密码、Token、身份证号等敏感信息,不要直接放进 __repr__:
python
class ApiClient:
def __init__(self, base_url, token):
self.base_url = base_url
self.token = token
def __repr__(self):
return f"ApiClient(base_url={self.base_url!r}, token='***')"
这是日志安全中的重要细节。
九、实践案例:电商订单对象如何设计?
假设我们正在做一个电商系统,有订单对象:
python
class Order:
def __init__(self, order_id, user_id, amount, status):
self.order_id = order_id
self.user_id = user_id
self.amount = amount
self.status = status
如果不实现任何字符串方法,日志可能是:
python
<__main__.Order object at 0x...>
不利于排查问题。
我们可以这样设计:
python
class Order:
def __init__(self, order_id, user_id, amount, status):
self.order_id = order_id
self.user_id = user_id
self.amount = amount
self.status = status
def __repr__(self):
return (
f"Order("
f"order_id={self.order_id!r}, "
f"user_id={self.user_id!r}, "
f"amount={self.amount!r}, "
f"status={self.status!r})"
)
def __str__(self):
return f"订单 {self.order_id},金额 ¥{self.amount:.2f},状态:{self.status}"
order = Order("O20260001", "U1001", 299.9, "paid")
print(repr(order))
print(str(order))
输出:
python
Order(order_id='O20260001', user_id='U1001', amount=299.9, status='paid')
订单 O20260001,金额 ¥299.90,状态:paid
这里的设计思路是:
text
__repr__ 用于日志、调试、开发者排错
__str__ 用于页面展示、CLI 输出、业务文本
这样既满足工程可维护性,又兼顾用户体验。
十、实践案例:数据分析中的对象展示
在数据科学或自动化脚本中,我们经常封装任务结果。
python
class AnalysisResult:
def __init__(self, total_rows, valid_rows, error_rows):
self.total_rows = total_rows
self.valid_rows = valid_rows
self.error_rows = error_rows
@property
def success_rate(self):
if self.total_rows == 0:
return 0
return self.valid_rows / self.total_rows
def __repr__(self):
return (
f"AnalysisResult("
f"total_rows={self.total_rows!r}, "
f"valid_rows={self.valid_rows!r}, "
f"error_rows={self.error_rows!r})"
)
def __str__(self):
return (
f"共处理 {self.total_rows} 行,"
f"有效 {self.valid_rows} 行,"
f"异常 {self.error_rows} 行,"
f"成功率 {self.success_rate:.2%}"
)
result = AnalysisResult(1000, 963, 37)
print(result)
print(repr(result))
输出:
python
共处理 1000 行,有效 963 行,异常 37 行,成功率 96.30%
AnalysisResult(total_rows=1000, valid_rows=963, error_rows=37)
这个例子很适合 Python教程 中介绍面向对象,也很适合真实 Python实战 中封装 ETL、数据校验、日志分析结果。
十一、dataclass 如何自动生成 __repr__?
现代 Python 开发中,dataclasses 非常常用。它可以自动帮我们生成 __init__ 和 __repr__。
python
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
role: str
user = User("Alice", 18, "admin")
print(user)
print(repr(user))
输出:
python
User(name='Alice', age=18, role='admin')
User(name='Alice', age=18, role='admin')
这就是为什么很多业务数据对象推荐使用 @dataclass。它让代码更少,也让调试输出更清晰。
如果某个字段不希望出现在 repr 中,比如密码,可以这样:
python
from dataclasses import dataclass, field
@dataclass
class User:
name: str
age: int
password: str = field(repr=False)
user = User("Alice", 18, "secret123")
print(user)
输出:
python
User(name='Alice', age=18)
这在处理敏感数据时非常实用。
十二、__repr__ 和 __str__ 与格式化输出
Python 中有多种把对象转换成字符串的方式:
python
str(obj)
repr(obj)
print(obj)
f"{obj}"
f"{obj!r}"
"{}".format(obj)
"{!r}".format(obj)
示例:
python
class Book:
def __init__(self, title):
self.title = title
def __repr__(self):
return f"Book(title={self.title!r})"
def __str__(self):
return f"《{self.title}》"
book = Book("流畅的 Python")
print(f"{book}")
print(f"{book!r}")
输出:
python
《流畅的 Python》
Book(title='流畅的 Python')
规则是:
text
f"{obj}" 使用 str(obj),即 __str__
f"{obj!r}" 使用 repr(obj),即 __repr__
因此,在日志中我经常建议写:
python
logger.info("处理订单:%r", order)
或者:
python
print(f"调试对象:{order!r}")
这样可以明确使用 __repr__,避免误用面向用户的 __str__。
十三、常见错误与避坑指南
错误一:__repr__ 返回的不是字符串
python
class User:
def __repr__(self):
return 123
调用:
python
repr(User())
会报错:
python
TypeError: __repr__ returned non-string
__repr__ 和 __str__ 必须返回字符串。
正确写法:
python
class User:
def __repr__(self):
return "User()"
错误二:在 __repr__ 中做复杂计算或外部请求
坏例子:
python
class User:
def __repr__(self):
data = query_database()
return f"User(data={data})"
这是非常不推荐的。
原因是 repr() 可能在日志、调试器、异常输出、容器展示中被频繁调用。如果 __repr__ 很慢或者有副作用,会让系统变得不可预测。
最佳实践:
text
__repr__ 应该快速、稳定、无副作用。
错误三:泄露敏感信息
坏例子:
python
class User:
def __init__(self, username, password):
self.username = username
self.password = password
def __repr__(self):
return f"User(username={self.username!r}, password={self.password!r})"
如果日志被收集到平台上,密码就泄露了。
更安全的写法:
python
class User:
def __init__(self, username, password):
self.username = username
self.password = password
def __repr__(self):
return f"User(username={self.username!r}, password='***')"
错误四:__str__ 太技术化
如果 __str__ 是给用户看的,就不要输出过多内部实现细节。
不友好的写法:
python
Order(order_id='A001', user_id='U001', amount=199.0, status='paid')
更友好的写法:
python
订单 A001 已支付,金额 ¥199.00
十四、操作性总结:项目中应该怎么写?
可以遵循下面这套规则。
1. 业务核心对象优先写 __repr__
例如:
python
class Payment:
def __repr__(self):
return (
f"Payment("
f"id={self.id!r}, "
f"amount={self.amount!r}, "
f"status={self.status!r})"
)
日志排查非常好用。
2. 需要展示给用户时再写 __str__
例如:
python
class Payment:
def __str__(self):
return f"支付单 {self.id}:¥{self.amount:.2f},状态:{self.status}"
3. 能用 dataclass 就不要手写重复代码
python
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
它会自动生成清晰的 __repr__。
4. 敏感字段必须隐藏
python
from dataclasses import dataclass, field
@dataclass
class Token:
name: str
value: str = field(repr=False)
5. 日志中优先使用 %r
python
logger.info("创建用户成功:%r", user)
这会调用 repr(user),更适合开发者排查问题。
十五、一张图理解二者关系
text
对象
│
├── repr(obj)
│ └── 调用 obj.__repr__()
│ └── 面向开发者:准确、明确、利于调试
│
└── str(obj)
└── 优先调用 obj.__str__()
└── 面向用户:友好、自然、适合展示
如果没有 __str__:
str(obj) 会退回使用 __repr__
常见触发场景:
text
print(obj) -> str(obj)
str(obj) -> __str__
repr(obj) -> __repr__
f"{obj}" -> __str__
f"{obj!r}" -> __repr__
交互式环境直接输入对象 -> __repr__
列表/字典中展示元素 -> 元素的 __repr__
日志调试 -> 推荐使用 __repr__
十六、从更高层看:它们也是 Python 数据模型的一部分
__repr__ 和 __str__ 并不是孤立技巧,而是 Python 数据模型中的一部分。
Python 的数据模型通过魔术方法定义各种协议:
text
__len__ -> 长度协议
__iter__ -> 迭代协议
__enter__ -> 上下文管理协议
__call__ -> 可调用协议
__repr__ -> 开发者字符串表示协议
__str__ -> 用户字符串表示协议
这正是 Python 优雅的地方:语言把很多行为开放给对象自己定义。
你希望对象能被 len() 使用,就实现 __len__。
你希望对象能被 for 遍历,就实现 __iter__。
你希望对象打印得更清楚,就实现 __repr__ 和 __str__。
这种设计让自定义对象可以像内置类型一样自然地融入 Python 生态。
结语:好的字符串表示,是写给未来自己的温柔
很多人学习 Python 时,会觉得 __repr__ 和 __str__ 是小知识点。但在真实项目中,它们直接影响调试体验、日志质量、错误排查效率和代码可维护性。
当凌晨线上报警响起,你打开日志,看到的是:
python
<__main__.Order object at 0x...>
你会痛苦。
但如果你看到的是:
python
Order(order_id='O20260001', user_id='U1001', amount=299.9, status='paid')
你会感谢曾经认真写下 __repr__ 的自己。
所以,最后给出一句实践建议:
核心对象写
__repr__,展示对象写__str__;
__repr__要准确安全,__str__要友好自然。
Python 编程的魅力,不只在于语法简洁,也在于这些细节能让代码越来越像一门精心打磨的语言。愿你在每一次打印对象、记录日志、排查问题时,都能感受到这种温柔而强大的设计。
欢迎在评论区分享:你有没有因为对象打印不清晰而排查过很久的问题?你在项目里更常重写 __repr__,还是 __str__?