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

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__

评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值