第30课:魔术方法(str 与 repr)
学习目标
完成本课学习后,你将能够:
- 理解魔术方法(Dunder方法)的概念及其在Python类中的作用。
- 清晰区分并正确实现
__str__和__repr__两个核心魔术方法。 - 掌握在自定义类中实现友好用户输出和开发者调试输出的技巧。
- 了解其他常见的魔术方法(如
__len__,__add__)的基本用法。
核心概念
什么是魔术方法?
魔术方法(Magic Methods),也称为"Dunder方法"(Double Underscore的缩写),是Python中一些具有特殊名称(以双下划线开头和结尾)的方法。它们不是你直接调用的,而是在特定操作发生时由Python解释器自动调用。它们是Python面向对象编程的"魔法"所在。
你可以把它们想象成赋予你的类特殊能力的"开关"。例如,当你想让对象能够被打印、被比较、或像数字一样相加时,就需要实现对应的魔术方法。
__str__ 与 __repr__:给对象一个"自我介绍"
这两个方法都用于获取对象的字符串表示形式,但用途不同:
-
__repr__(self):- 目的:供开发者(和调试器)使用的"官方"或"内部"表示。它应该尽可能明确、无歧义,理想情况下可以用
eval()重新创建该对象。 - 调用方式:当在交互式解释器中直接输入对象名,或调用内置函数
repr()时触发。 - 类比:对象的技术文档或DNA档案,详细且精确。
- 目的:供开发者(和调试器)使用的"官方"或"内部"表示。它应该尽可能明确、无歧义,理想情况下可以用
-
__str__(self):- 目的:供最终用户使用的"友好"或"非正式"字符串表示。它应该易于阅读。
- 调用方式:当使用
print()函数或调用内置函数str()时触发。 - 类比:对象的名片或简介,清晰且易懂。
关键规则:
- 如果你只实现其中一个,优先实现
__repr__。因为当__str__未定义时,Python会回退去调用__repr__。 __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(半径)。为其实现:
__init__方法。__str__方法,返回"Circle with radius: <半径值>"。__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))。
- 实现
__init__,__str__,__repr__。 - 实现
__add__方法,使两个向量可以相加:(x1, y1) + (x2, y2) = (x1+x2, y1+y2)。 - 实现
__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)
常见错误
- 混淆
__str__和__repr__的用途:在__str__里返回了过于技术性的信息,或者在__repr__里返回了不够精确的描述。记住:__repr__给人看,__str__给用户看。 - 忘记返回字符串:魔术方法必须返回一个字符串对象,否则会引发
TypeError。 - 在
__repr__中格式不统一:为了可读性和可调试性,__repr__的输出最好遵循类名(属性1=值1, ...)的模式。 - 试图在
__str__或__repr__中打印:应该使用return返回字符串,而不是在方法内部调用print()。 - 不处理边界情况:例如,在
__add__中没有检查参数类型,直接进行操作,导致程序崩溃。
小结
本课我们深入学习了Python中两个至关重要的魔术方法:
__repr__:为开发者服务的精确、明确的对象表示,用于调试和日志记录。这是你应该为类实现的第一个魔术方法。__str__:为最终用户服务的友好、易读的对象表示,用于print()输出。
我们还瞥见了魔术方法的强大之处,通过 __len__ 和 __add__ 让自定义类像内置类型一样(如列表、数字)支持 len() 函数和 + 运算符。
核心要点:
- 魔术方法通过
__methodname__命名,在特定场景下被Python自动调用。 - 优先实现
__repr__,它会在__str__缺失时作为后备。 - 善用魔术方法可以极大增强你自定义类的可用性和集成度,使它们"感觉"像Python原生类型。
下一课,我们将学习 @property 装饰器,它是实现优雅访问控制的另一大法宝。
练习编辑器
python
Loading...