欢迎来到 Python 学习计划的第 43 天!🎉
恭喜你!昨天我们完成了 OOP 核心阶段(第 39-42 天) 的三大特征(继承、多态、封装)学习。从今天开始,我们将进入 OOP 高级阶段(第 43-51 天)。
今天我们要揭开 Python 对象的神秘面纱——魔法方法(Magic Methods)。它们是让对象变得“智能”、“Pythonic"的关键。我们将重点探讨最常用的两个魔法方法:__str__ 与 __repr__。
一、什么是魔法方法(Magic Methods)?
1. 定义
魔法方法 是 Python 类中具有特殊意义的方法。它们的名称以双下划线开头和结尾(如 __init__, __str__),因此也被称为 Dunder Methods(Double Underline)。
2. 特点
- 自动调用:不需要手动调用,Python 在特定场景下会自动触发。
- 增强功能:让自定义对象支持内置函数(如
len(), print(), + 运算)。 - 协议实现:实现迭代器、上下文管理器等高级功能的基础。
3. 常见魔法方法一览
方法 | 触发场景 | 作用 |
|---|
__init__
| 实例化时 | 初始化对象(已学) |
__str__
| print(obj) 或 str(obj)
| 用户友好的字符串表示 |
__repr__
| 交互式命令行或 repr(obj) | 开发者友好的字符串表示 |
__len__
| len(obj)
| 返回对象长度 |
__call__
| obj()
| 让对象可像函数一样调用 |
__add__
| obj1 + obj2
| 定义加法运算 |
💡 今日重点:__str__ 与 __repr__。它们是调试和展示对象信息的基石。二、__str__ vs __repr__ 深度解析
1. 默认行为
如果不定义这两个方法,打印对象时会显示默认的内存地址信息,对人类不友好。
class Person: def __init__(self, name, age): self.name = name self.age = agep = Person("Alice", 25)print(p) # 输出:<__main__.Person object at 0x7f8b1c2a3d90># ❌ 无用信息,不知道对象内部数据
2. __str__:用户友好的表示
- 目的:面向最终用户,提供易读的字符串。
- 触发:
print(obj), str(obj)。 - 原则:简洁、美观、易理解。
3. __repr__:开发者友好的表示
- 目的:面向开发者,用于调试和日志。
- 触发:交互式命令行直接输入对象,
repr(obj),或在列表中打印对象。 - 原则:准确、详细, ideally 能直接用于 recreate 对象(
eval(repr(obj)) == obj)。
4. 对比示例
class Person: def __init__(self, name, age): self.name = name self.age = age def __str__(self): # 用户看:简洁 return f"{self.name} ({self.age}岁)" def __repr__(self): # 开发者看:详细,可还原 return f"Person('{self.name}', {self.age})"p = Person("Alice", 25)# 触发 __str__print(p) # Alice (25 岁)print(str(p)) # Alice (25 岁)# 触发 __repr__print(repr(p)) # Person('Alice', 25)p # 在交互式命令行直接输入 p 会显示 Person('Alice', 25)# 在列表中(优先使用 __repr__)print([p]) # [Person('Alice', 25)]
💡 关键区别:
print(obj) 优先找 __str__,找不到则找 __repr__。- 容器(如 list, dict)中的对象始终使用
__repr__。
三、最佳实践
1. 始终实现 __repr__
无论什么类,都建议至少实现 __repr__。这样在调试时能看到有意义的信息。
class Product: def __init__(self, name, price): self.name = name self.price = price def __repr__(self): return f"Product('{self.name}', {self.price})"
2. 按需实现 __str__
如果 __repr__ 已经足够友好,可以不实现 __str__(会 fallback 到 __repr__)。如果需要更简洁的用户展示,再实现 __str__。
class Product: # ... __init__ ... def __repr__(self): return f"Product('{self.name}', {self.price})" def __str__(self): return f"{self.name}: ${self.price}" # 更简洁p = Product("Laptop", 999)print(p) # Laptop: $999 (__str__)print([p]) # [Product('Laptop', 999)] (__repr__)
3. 避免副作用
魔法方法应尽量纯净,不要在其中修改对象状态或执行耗时操作(如网络请求)。
# ❌ 不推荐def __str__(self): self.count += 1 # 修改状态 return str(self.count)# ✅ 推荐def __str__(self): return str(self.count)
四、其他常用魔法方法预览
虽然今天重点讲字符串表示,但了解其他魔法方法有助于构建更强大的对象。
1. __len__:支持 len()
class Collection: def __init__(self, items): self.items = items def __len__(self): return len(self.items)c = Collection([1, 2, 3])print(len(c)) # 3
2. __getitem__:支持索引访问
class MyList: def __init__(self, data): self.data = data def __getitem__(self, index): return self.data[index]ml = MyList([10, 20, 30])print(ml[1]) # 20
3. __eq__:支持 == 比较
class Point: def __init__(self, x, y): self.x = x self.y = y def __eq__(self, other): return self.x == other.x and self.y == other.yp1 = Point(1, 2)p2 = Point(1, 2)print(p1 == p2) # True (默认比较的是内存地址)
📌 注意:这些方法我们将在后续几天(第 44-45 天)深入讲解。五、常见误区与注意事项
1. 返回值必须是字符串
__str__ 和 __repr__ 必须返回 str 类型,否则会报错。
class Wrong: def __str__(self): return 123 # ❌ TypeError: __str__ returned non-string (type int)class Correct: def __str__(self): return "123" # ✅
2. 不要混淆用途
- 不要在
__repr__ 中写过于简化的信息(不利于调试)。 - 不要在
__str__ 中写过于技术化的信息(不利于用户理解)。
3. 性能考虑
__repr__ 可能在日志中频繁调用,避免在其中执行复杂计算。
六、实战练习
练习 1:实现书籍类
创建一个 Book 类,包含 title, author, price。
__repr__:返回 Book('title', 'author', price) 格式。__str__:返回 《title》by author ($price) 格式。
class Book: def __init__(self, title, author, price): self.title = title self.author = author self.price = price def __repr__(self): return f"Book('{self.title}', '{self.author}', {self.price})" def __str__(self): return f"《{self.title}》by {self.author} (${self.price})"b = Book("Python 入门", "Guido", 59.9)print(b) # 《Python 入门》by Guido ($59.9)print(repr(b)) # Book('Python 入门', 'Guido', 59.9)print([b]) # [Book('Python 入门', 'Guido', 59.9)]
练习 2:调试助手
创建一个 DataRecord 类,包含任意关键字参数。实现 __repr__ 使其能显示所有属性,方便调试。
class DataRecord: def __init__(self, **kwargs): self.__dict__.update(kwargs) # 动态设置属性 def __repr__(self): # 获取所有属性并格式化 attrs = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items()) return f"DataRecord({attrs})"record = DataRecord(id=1, name="Alice", active=True)print(record) # DataRecord(id=1, name='Alice', active=True)
七、总结
知识点 | 说明 |
|---|
魔法方法 | 双下划线开头结尾,自动调用,增强对象功能 |
__str__
| 用户友好,print() 触发,简洁易读 |
__repr__
| 开发者友好,repr() 触发,详细准确 |
优先级 | print() 优先 __str__,容器优先 __repr__
|
最佳实践 | 始终实现 __repr__,按需实现 __str__ |
返回值 | 必须返回字符串 |
📌 明日预告:魔法方法之 __call__
明天我们将进入 高级 OOP 特性第二天!
- 主题:
__call__ 让对象可调用 - 核心问题:
- 如何让一个对象像函数一样被调用?
obj() __call__ 方法的参数如何定义?- 实际应用场景有哪些?(装饰器类、回调函数)
- 它与普通方法有什么区别?
- 如何结合
__init__ 实现可配置的可调用对象?
💡 提前思考:如果有一个类 Multiplier,初始化时传入倍数 n,如何让 obj(5) 返回 5 * n?
掌握魔法方法,让你的对象更“智能”!继续加油!🚀