当前位置:首页>python>【一起学 Python】第 43 天:高级 OOP 特性之魔法方法 —— __str__ 与 __repr__ 详解

【一起学 Python】第 43 天:高级 OOP 特性之魔法方法 —— __str__ 与 __repr__ 详解

  • 2026-09-08 15:10:20
【一起学 Python】第 43 天:高级 OOP 特性之魔法方法 —— __str__ 与 __repr__ 详解

欢迎来到 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__ 让对象可调用
  • 核心问题:
    1. 如何让一个对象像函数一样被调用?obj()
    2. __call__ 方法的参数如何定义?
    3. 实际应用场景有哪些?(装饰器类、回调函数)
    4. 它与普通方法有什么区别?
    5. 如何结合 __init__ 实现可配置的可调用对象?

💡 提前思考:如果有一个类 Multiplier,初始化时传入倍数 n,如何让 obj(5) 返回 5 * n?

掌握魔法方法,让你的对象更“智能”!继续加油!🚀

最新文章

随机文章