这是一个专门针对 __str__ 和 __repr__ 进行了代码深挖,并整合了所有 Class 核心要点的完整总结。
一、 核心:__str__ 与 __repr__ 深度解析
在 Python 中,如果你直接打印一个类实例,通常会看到类似 <__main__.Student object at 0x102...> 这样不直观的信息。为了让输出更有意义,我们需要这两个魔术方法。
1. 代码演示
class Book:
def __init__(self, title, author, price):
self.title = title
self.author = author
self.price = price
# 【面向用户】让 print() 更好看
def __str__(self):
return f"《{self.title}》- {self.author} 著"
# 【面向开发】让调试更精准,通常返回“创建该对象的代码模样”
def __repr__(self):
return f"Book(title='{self.title}', author='{self.author}', price={self.price})"
# --- 测试代码 ---
b = Book("Python入门", "Guido", 59.9)
# 场景 1:使用 print() 或 str()
print(b) # 输出: 《Python入门》- Guido 著
print(str(b)) # 输出: 《Python入门》- Guido 著
# 场景 2:在交互式命令行直接输入变量名 (或者调试器查看)
# >>> b
# 输出: Book(title='Python入门', author='Guido', price=59.9)
# 场景 3:当对象存在于容器(如列表)中时
bookshelf = [b, Book("流浪地球", "刘慈欣", 45.0)]
print(bookshelf)
# 输出: [Book(title='Python入门', author='Guido', price=59.9), Book(title='流浪地球', author='刘慈欣', price=45.0)]
# 注意:容器内部打印使用的是 __repr__,而不是 __str__!
2. 关键区别总结
| 方法 | 触发时机 | 目标受众 | 编写建议 |
|---|---|---|---|
__str__ | print(), str() | 终端用户 | 追求简单、易读,像一句话。 |
__repr__ | 直接输入变量、repr()、列表/字典内部显示 | 开发者/调试 | 追求无歧义,最好写成 类名(参数=值)。 |
注:如果你只写 __repr__ 而不写 __str__,Python 在需要打印时会“借用” __repr__;但反过来不行。
二、 Python 类(Class)知识全图谱
1. 结构与生命周期
class ClassName:使用大驼峰命名。__init__(self, ...):初始化方法。不能返回任何值(除了None)。self:必须作为第一个参数,代表当前的实例。调用时由 Python 自动传入。
2. 两种属性(极其重要)
- 实例属性:在
__init__里定义的self.x。每个对象独有一份。 - 类属性:在所有函数外定义的变量。所有对象共享一份。
- 大坑: 永远不要把
list或dict作为类属性,除非你真的希望所有人在一个篮子里放东西。
- 大坑: 永远不要把
3. 解决“多个 init”问题
Python 不支持函数重载(同名不同参),我们使用 @classmethod 提供额外的初始化路径:
class Date:
def __init__(self, year, month, day):
self.year, self.month, self.day = year, month, day
@classmethod
def from_string(cls, date_str): # 通过 "2023-10-01" 初始化
y, m, d = map(int, date_str.split('-'))
return cls(y, m, d) # cls 代表 Date 类本身
4. 三种方法对比
- 实例方法:带
self,操作对象数据。 - 类方法 (
@classmethod):带cls,通常用于工厂模式(创建对象)。 - 静态方法 (
@staticmethod):不带self或cls,只是逻辑上属于这个类(类似普通函数)。
5. 面向对象三大特性
- 继承:
class Child(Parent):。使用super().__init__()激活父类属性。 - 封装:使用
__name(双下划线)定义私有属性,保护数据。 - 多态:不同类实现同名方法(如
draw()),调用者无需关心对象具体是谁。
三、 避坑指南总结
- 漏写
self:- 内部调用方法必须写
self.method()。 - 访问内部属性必须写
self.attr。
- 内部调用方法必须写
- 默认参数陷阱:
def __init__(self, items=[])是极其危险的!所有实例会共用同一个列表。应该用items=None,然后在函数内赋值self.items = []。
- 继承被覆盖:
- 如果子类写了
__init__,父类的__init__就不会自动执行,必须手动调super()。
- 如果子类写了
- 混淆类属性与实例属性:
- 修改类属性会通过
ClassName.attr = val改变所有人。 - 通过
self.attr = val修改,如果原本是类属性,会变成在该实例上新建一个同名实例属性,掩盖掉类属性。
- 修改类属性会通过
四、 最后的建议
- 什么时候写类? 当你发现有一堆变量(数据)和一堆函数(行为)总是形影不离时,就该把它们揉成一个类。
- 好的类长什么样? 属性在
__init__里一目了然,有清晰的__repr__方便调试,复杂的初始化交给@classmethod。