Python面向对象:__str__与__repr__打印对象格式

一、开篇:你的对象打印出来好看吗?
当你print(obj)时,Python内部调用了什么?为什么有时输出<__main__.MyClass object at 0x7f...>这种让人抓狂的格式,有时又很友好?
⌨️ 答案在两个魔法方法中:
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
# 默认输出——毫无信息量
p = Product("机械键盘", 399)
print(p) # <__main__.Product object at 0x7f8b...>
print(repr(p)) # <__main__.Product object at 0x7f8b...>
# ✅ 定义了__str__和__repr__之后:
class ProductV2:
def __init__(self, name, price):
self.name = name
self.price = price
def __str__(self):
"""给用户看的——友好、可读"""
return f"{self.name}(¥{self.price})"
def __repr__(self):
"""给开发者看的——精确、可复现"""
return f"ProductV2(name='{self.name}', price={self.price})"
p2 = ProductV2("机械键盘", 399)
print(p2) # 机械键盘(¥399) —— __str__
print(repr(p2)) # ProductV2(name='机械键盘', price=399) —— __repr__
# 在交互环境中直接输入p2:
# >>> p2
# ProductV2(name='机械键盘', price=399) —— 交互环境用__repr__
💡 简单记忆:__str__是给用户看的(print()),__repr__是给开发者看的(调试、交互环境)。前者追求可读,后者追求精确和无歧义。
二、str:用户友好的字符串
2.1 何时被调用
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
def __str__(self):
return f"《{self.title}》作者:{self.author}({self.pages}页)"
book = Book("Python编程", "张三", 500)
# __str__在这些情况下被调用:
print(book) # 1. print()
print(str(book)) # 2. str()
print(f"我正在读{book}") # 3. f-string格式化
print("书籍信息:%s" % book) # 4. %s格式化
print("书籍信息:{}".format(book)) # 5. format()
# 如果只定义了__str__没有__repr__
# print(book) 正常调用__str__
# repr(book) 会退回到默认的 <__main__.Book object at ...>
2.2 设计好的__str__
from datetime import datetime
class Order:
"""订单类——设计清晰的__str__"""
def __init__(self, order_id, customer, items, created_at=None):
self.order_id = order_id
self.customer = customer
self.items = items # list of (name, quantity, price)
self.created_at = created_at or datetime.now()
def __str__(self):
lines = [
f"订单 #{self.order_id}",
f"客户: {self.customer}",
f"时间: {self.created_at.strftime('%Y-%m-%d %H:%M')}",
"-" * 40,
]
total = 0
for name, qty, price in self.items:
subtotal = qty * price
total += subtotal
lines.append(f" {name} ×{qty} ¥{price} = ¥{subtotal}")
lines.append("-" * 40)
lines.append(f"合计: ¥{total}")
return "\n".join(lines)
order = Order("ORD-2024001", "张三", [
("机械键盘", 1, 399),
("鼠标", 2, 199),
("鼠标垫", 3, 29),
])
print(order)
三、repr:开发者的精确表示
3.1 黄金法则
# 💡 __repr__的黄金法则(来自官方文档):
# "如果可能,__repr__应该返回一个字符串
# 用这个字符串可以通过eval()重新创建相同值的对象"
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
# 返回的字符串可以直接用来重新创建对象
return f"Point(x={self.x}, y={self.y})"
def __str__(self):
return f"({self.x}, {self.y})"
p = Point(3, 4)
print(repr(p)) # Point(x=3, y=4)
# 验证黄金法则
import ast
# p2 = eval(repr(p)) # 重新创建了一个Point对象!
# print(p2.x, p2.y) # 3 4
# ⚠️ 但不是所有对象都能这样"还原"
# 对于复杂对象(数据库连接、文件句柄等)
# __repr__至少应该明确描述对象
3.2 __repr__在调试中的作用
# 列表、字典中的对象显示用的是__repr__
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __repr__(self):
return f"Student({self.name!r}, {self.score})"
# !r表示对name调用repr()——显示带引号的字符串
def __str__(self):
return f"{self.name}({self.score}分)"
students = [
Student("张三", 85),
Student("李四", 92),
Student("王五", 78),
]
# 在列表/字典中——用__repr__
print(students)
# [Student('张三', 85), Student('李四', 92), Student('王五', 78)]
# 调试时信息量十足!
# 在logging中——用__repr__
import logging
logging.basicConfig(level=logging.DEBUG)
# logging.debug("学生列表: %r", students) # %r 调用__repr__
四、两者对比和交互
4.1 优先级规则
class Demo:
def __str__(self):
return "这是__str__"
def __repr__(self):
return "这是__repr__"
d = Demo()
# 各种场景的调用:
print(d) # __str__ —— print()优先使用__str__
print(str(d)) # __str__
print(repr(d)) # __repr__
print(f"{d}") # __str__ —— f-string默认用__str__
print(f"{d!r}") # __repr__ —— !r强制用__repr__
print(f"{d!s}") # __str__ —— !s强制用__str__
# 只有__repr__没有__str__时
class OnlyRepr:
def __repr__(self):
return "OnlyRepr实例"
or_obj = OnlyRepr()
print(or_obj) # OnlyRepr实例 —— print()回退到__repr__
print(str(or_obj)) # OnlyRepr实例 —— str()也回退到__repr__
4.2 标准做法
# ✅ 最佳实践:至少定义__repr__
# 如果只能定义一个,选择__repr__——它是__str__的后备
class Card:
"""扑克牌"""
SUITS = {"S": "♠", "H": "♥", "D": "♦", "C": "♣"}
RANKS = {1: "A", 11: "J", 12: "Q", 13: "K"}
def __init__(self, suit, rank):
self.suit = suit
self.rank = rank
def __repr__(self):
"""精确表示——用于调试"""
return f"Card(suit='{self.suit}', rank={self.rank})"
def __str__(self):
"""友好显示——给用户看"""
suit_symbol = self.SUITS.get(self.suit, self.suit)
rank_symbol = self.RANKS.get(self.rank, str(self.rank))
return f"{suit_symbol}{rank_symbol}"
# 测试
card = Card("S", 1) # 黑桃A
print(card) # ♠A
print(repr(card)) # Card(suit='S', rank=1)
# 在列表中
hand = [Card("S", 1), Card("H", 13), Card("D", 11)]
print(hand)
# [Card(suit='S', rank=1), Card(suit='H', rank=13), Card(suit='D', rank=11)]
五、总结
__str__和__repr__是Python对象的"名片"。前者面向用户(可读),后者面向开发者(精确)。
💡 核心规则:
| 方法 | 用途 | 调用方式 | 优先级 |
|---|---|---|---|
__str__ | 用户友好 | print(), str(), f"{obj}" | 高(print优先用) |
__repr__ | 开发者精确 | repr(), f"{obj!r}", 交互环境 | 低(str回退到它) |
✅ 黄金法则:__repr__返回的字符串应尽量能用eval()重建对象。至少定义__repr__——这样print()也能用。如果需要友好格式,再叠加__str__。

489

被折叠的 条评论
为什么被折叠?



