30·模块七:面向对象编程进阶

魔术方法(如 __str__, __repr__)

oopmagic-methodsdunder

第30课:魔术方法(strrepr

学习目标

完成本课学习后,你将能够:

  1. 理解魔术方法(Dunder方法)的概念及其在Python类中的作用。
  2. 清晰区分并正确实现 __str____repr__ 两个核心魔术方法。
  3. 掌握在自定义类中实现友好用户输出和开发者调试输出的技巧。
  4. 了解其他常见的魔术方法(如 __len__, __add__)的基本用法。

核心概念

什么是魔术方法?

魔术方法(Magic Methods),也称为"Dunder方法"(Double Underscore的缩写),是Python中一些具有特殊名称(以双下划线开头和结尾)的方法。它们不是你直接调用的,而是在特定操作发生时由Python解释器自动调用。它们是Python面向对象编程的"魔法"所在。

你可以把它们想象成赋予你的类特殊能力的"开关"。例如,当你想让对象能够被打印、被比较、或像数字一样相加时,就需要实现对应的魔术方法。

__str____repr__:给对象一个"自我介绍"

这两个方法都用于获取对象的字符串表示形式,但用途不同:

  • __repr__(self)

    • 目的:供开发者(和调试器)使用的"官方"或"内部"表示。它应该尽可能明确、无歧义,理想情况下可以用 eval() 重新创建该对象。
    • 调用方式:当在交互式解释器中直接输入对象名,或调用内置函数 repr() 时触发。
    • 类比:对象的技术文档或DNA档案,详细且精确。
  • __str__(self)

    • 目的:供最终用户使用的"友好"或"非正式"字符串表示。它应该易于阅读。
    • 调用方式:当使用 print() 函数或调用内置函数 str() 时触发。
    • 类比:对象的名片或简介,清晰且易懂。

关键规则

  1. 如果你只实现其中一个,优先实现 __repr__。因为当 __str__ 未定义时,Python会回退去调用 __repr__
  2. __repr__ 的返回字符串通常使用类似构造函数的格式,如 类名(参数1=值1, 参数2=值2)

代码示例

class Product:
    """一个表示商品的类"""
    
    def __init__(self, name, price, quantity):
        self.name = name
        self.price = price  # 单价(元)
        self.quantity = quantity  # 库存数量

    # __repr__: 供开发者使用的精确描述
    def __repr__(self):
        # 使用 f-string 格式化,模拟构造函数调用
        return f"Product(name='{self.name}', price={self.price}, quantity={self.quantity})"
    
    # __str__: 供用户使用的友好描述
    def __str__(self):
        # 计算库存总价值,并格式化输出
        total_value = self.price * self.quantity
        return f"商品: {self.name}\n单价: ¥{self.price:.2f}\n库存: {self.quantity}件\n库存总价值: ¥{total_value:.2f}"

    # 其他有用的魔术方法示例
    def __len__(self):
        """使 len() 函数可以作用于对象,返回库存数量"""
        return self.quantity
    
    def __add__(self, other):
        """定义两个商品对象相加的规则(例如,合并库存)"""
        if not isinstance(other, Product):
            raise TypeError("只能与另一个商品对象相加")
        if self.name != other.name:
            raise ValueError("只能合并同名商品的库存")
        # 创建一个新的Product对象,库存数量相加
        return Product(self.name, self.price, self.quantity + other.quantity)


# 创建实例
laptop = Product("高性能笔记本电脑", 7999.99, 15)
mouse = Product("无线鼠标", 199.5, 100)

# 1. 查看 __repr__ 和 __str__ 的区别
print("--- 测试 __repr__ 与 __str__ ---")
# 交互式环境会显示 __repr__ 结果
print(f"repr: {repr(laptop)}")
# print() 函数会调用 __str__
print("str:")
print(laptop)
print()
print(f"repr(mouse): {repr(mouse)}")
print(f"str(mouse): {str(mouse)}")
print()

# 2. 测试其他魔术方法
print("--- 测试 __len__ ---")
print(f"laptop 的库存数量 (len(laptop)): {len(laptop)}")
print(f"mouse 的库存数量 (len(mouse)): {len(mouse)}")
print()

print("--- 测试 __add__ ---")
try:
    # 创建另一个同名商品实例
    laptop2 = Product("高性能笔记本电脑", 7999.99, 5)
    # 尝试合并库存
    merged_laptop = laptop + laptop2
    print(f"合并后的商品信息:\n{merged_laptop}")
except (TypeError, ValueError) as e:
    print(f"合并失败: {e}")

# 尝试相加不同商品,会触发异常
try:
    invalid_merge = laptop + mouse
except (TypeError, ValueError) as e:
    print(f"尝试相加不同商品: {e}")

输出结果:

--- 测试 __repr__ 与 __str__ ---
repr: Product(name='高性能笔记本电脑', price=7999.99, quantity=15)
str:
商品: 高性能笔记本电脑
单价: ¥7999.99
库存: 15件
库存总价值: ¥119999.85

repr(mouse): Product(name='无线鼠标', price=199.5, quantity=100)
str(mouse): 商品: 无线鼠标
单价: ¥199.50
库存: 100件
库存总价值: ¥19950.00

--- 测试 __len__ ---
laptop 的库存数量 (len(laptop)): 15
mouse 的库存数量 (len(mouse)): 100

--- 测试 __add__ ---
合并后的商品信息:
商品: 高性能笔记本电脑
单价: ¥7999.99
库存: 20件
库存总价值: ¥159999.80
尝试相加不同商品: 只能合并同名商品的库存

实践练习

练习1:基础实现(简单)

创建一个 Circle 类,包含属性 radius(半径)。为其实现:

  1. __init__ 方法。
  2. __str__ 方法,返回 "Circle with radius: <半径值>"
  3. __repr__ 方法,返回 "Circle(<半径值>)"

测试代码:

c = Circle(5)
print(c)          # 应输出: Circle with radius: 5
print(repr(c))    # 应输出: Circle(5)

练习2:综合应用(中等)

基于上面的 Product 类,添加一个 __eq__ 魔术方法,用于判断两个商品对象是否相等。两个商品相等的条件是:名称、价格、库存数量都完全相同

测试代码:

p1 = Product("苹果", 3.5, 100)
p2 = Product("苹果", 3.5, 100)
p3 = Product("香蕉", 2.8, 50)
print(p1 == p2)  # 应输出: True
print(p1 == p3)  # 应输出: False

练习3:挑战题(困难)

创建一个 Vector 类,用于表示二维向量(如 (x, y))。

  1. 实现 __init__, __str__, __repr__
  2. 实现 __add__ 方法,使两个向量可以相加:(x1, y1) + (x2, y2) = (x1+x2, y1+y2)
  3. 实现 __mul__ 方法,使向量可以与标量(一个数字)相乘:(x, y) * n = (x*n, y*n)

测试代码:

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1)         # 应输出类似: Vector(1, 2)
print(v1 + v2)    # 应输出: Vector(4, 6)
print(v1 * 3)     # 应输出: Vector(3, 6)

常见错误

  1. 混淆 __str____repr__ 的用途:在 __str__ 里返回了过于技术性的信息,或者在 __repr__ 里返回了不够精确的描述。记住:__repr__ 给人看,__str__ 给用户看。
  2. 忘记返回字符串:魔术方法必须返回一个字符串对象,否则会引发 TypeError
  3. __repr__ 中格式不统一:为了可读性和可调试性,__repr__ 的输出最好遵循 类名(属性1=值1, ...) 的模式。
  4. 试图在 __str____repr__ 中打印:应该使用 return 返回字符串,而不是在方法内部调用 print()
  5. 不处理边界情况:例如,在 __add__ 中没有检查参数类型,直接进行操作,导致程序崩溃。

小结

本课我们深入学习了Python中两个至关重要的魔术方法:

  • __repr__:为开发者服务的精确、明确的对象表示,用于调试和日志记录。这是你应该为类实现的第一个魔术方法
  • __str__:为最终用户服务的友好、易读的对象表示,用于 print() 输出。

我们还瞥见了魔术方法的强大之处,通过 __len____add__ 让自定义类像内置类型一样(如列表、数字)支持 len() 函数和 + 运算符。

核心要点

  1. 魔术方法通过 __methodname__ 命名,在特定场景下被Python自动调用。
  2. 优先实现 __repr__,它会在 __str__ 缺失时作为后备。
  3. 善用魔术方法可以极大增强你自定义类的可用性和集成度,使它们"感觉"像Python原生类型。

下一课,我们将学习 @property 装饰器,它是实现优雅访问控制的另一大法宝。

练习编辑器

python
Loading...

继续学习

完成本课后,建议继续学习下一课「属性(@property)与访问控制」 以巩固所学知识。