第7章 7.4 类方法/静态方法/魔术方法
🎯 开场:为什么要学这个?
上一章我们学会了用「继承」让代码复用,用「多态」让不同对象说同一种话。但你有没有遇到过这种情况:
- 想给一个类直接调用,不需要先 new 一个对象?
- 想让两个对象比较大小,但不知道怎么让
==生效? - 想让自己的类打印出来好看一点,而不是显示
<__main__.Student object at 0x7fxxx>?
这些场景,类方法、静态方法、魔术方法 就是你的解决方案。
学完这章,你能:
1. 用 @classmethod 写工厂方法,一行代码创建对象
2. 用 @staticmethod 写工具函数,不依赖实例也能调用
3. 用 __str__/__repr__/__eq__ 让你的对象更好看、更好用
🧱 基础:三个概念一次讲透
1. 类方法 @classmethod —— 类的专属构造函数
是什么?
类方法是绑定到「类」而不是「实例」的方法。用 @classmethod 装饰器标记。
生活类比:
想象你要寄快递。你可以自己打包(手动创建对象),也可以让快递公司帮你打包(类方法帮你创建)。工厂方法就是「快递公司」——专业的事交给专业的人做。
为什么用?
当你需要用不同方式创建同一个类的对象时,构造函数不够用。类方法可以提供多种「生产线」。
怎么用?
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
# 类方法:另一种创建对象的方式
@classmethod
def from_dict(cls, data):
"""从字典创建学生对象"""
return cls(data['name'], data['score'])
# 普通方式创建
s1 = Student("小明", 85)
# 类方法方式创建 —— 一行代码搞定
data = {"name": "小红", "score": 92}
s2 = Student.from_dict(data)
print(s2.name, s2.score) # 输出:小红 92
解释:第 17 行调用 from_dict,类方法自动拿到 Student 类本身(cls),然后用它创建对象。

2. 静态方法 @staticmethod —— 不依赖类和实例的工具函数
是什么?
静态方法是「挂靠在类名下」的普通函数。用 @staticmethod 装饰器标记,但它不需要 self 或 cls 参数。
生活类比:
就像你家楼下的自助快递柜——它不属于任何快递公司,但任何快递都可以用。静态方法就是那个快递柜,跟哪个对象都没关系,但能在类的命名空间里统一管理。
为什么用?
当你有一段逻辑上属于这个类,但不依赖实例属性的代码时用它。把相关功能放在一起,代码更整洁。
怎么用?
class MathTool:
@staticmethod
def calculate_tax(price, rate=0.13):
"""计算税费(跟哪个实例都没关系)"""
return price * rate
@staticmethod
def format_money(amount):
"""格式化金额为字符串"""
return f"¥{amount:.2f}"
# 调用静态方法:直接用类名,不需要创建对象
tax = MathTool.calculate_tax(100)
print(tax) # 输出:13.0
print(MathTool.format_money(99.8)) # 输出:¥99.80
解释:第 13 行直接用 类名.方法名() 调用静态方法,不需要 new MathTool()。
3. 魔术方法 —— 让你的对象更聪明
是什么?
魔术方法(也叫「双下方法」或「dunder methods」)是以双下划线 __ 开头和结尾的特殊方法。Python 会在特定场景自动调用它们。
生活类比:
想象你有一个「智能音箱」。你说「现在几点」,它自动播放时间——你不需要手动触发,它在听特定关键词。魔术方法就是那个「关键词触发器」。
3.1 __str__ —— 控制 print 输出的外观
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __str__(self):
"""当你 print 这个对象时,Python 会调用这个方法"""
return f"学生{self.name},成绩{self.score}"
s = Student("小明", 85)
print(s) # 输出:学生小明,成绩85
print(str(s)) # 效果同上
3.2 __repr__ —— 控制调试时的外观
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __repr__(self):
"""给开发者看的,用于调试"""
return f"Student(name='{self.name}', score={self.score})"
s = Student("小明", 85)
print(repr(s)) # 输出:Student(name='小明', score=85)
注意:
__str__是给用户看的,__repr__是给开发者看的。如果只定义__repr__,print 也会用它。
3.3 __eq__ —— 控制 == 的比较逻辑
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __eq__(self, other):
"""定义两个学生什么时候算相等"""
if not isinstance(other, Student):
return False
return self.name == other.name and self.score == other.score
s1 = Student("小明", 85)
s2 = Student("小明", 85)
s3 = Student("小红", 90)
print(s1 == s2) # 输出:True(内容相同)
print(s1 == s3) # 输出:False
3.4 __hash__ —— 让对象可以放进 set 或当 dict 的键
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __eq__(self, other):
if not isinstance(other, Student):
return False
return self.name == other.name
def __hash__(self):
"""必须和 __eq__ 保持一致:相等的对象必须有相同的哈希值"""
return hash(self.name)
s1 = Student("小明", 85)
s2 = Student("小明", 90) # 名字相同,分数不同
# 现在可以放进 set 了
students = {s1, s2}
print(len(students)) # 输出:1(因为 s1 和 s2 被视为同一个)

🔥 实战:三个递进项目
项目 1:学生成绩管理器(5 分钟)
需求:创建学生对象,用类方法从不同数据源创建,比较两个学生是否相同。
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
@classmethod
def from_line(cls, line):
"""从 '姓名,分数' 格式的行创建"""
name, score = line.strip().split(',')
return cls(name, int(score))
@classmethod
def from_dict(cls, data):
"""从字典创建"""
return cls(data['name'], data['score'])
def __str__(self):
return f"📚 {self.name}:{self.score}分"
def __eq__(self, other):
if not isinstance(other, Student):
return False
return self.name == other.name
def __hash__(self):
return hash(self.name)
# 测试代码
s1 = Student("小明", 85)
s2 = Student.from_line("小红,92")
s3 = Student.from_dict({"name": "小李", "score": 88})
print(s1)
print(s2)
print(s3)
# 比较
s4 = Student("小明", 100) # 同名,不同分数
print(s1 == s4) # True(只比较名字)
#放进 set
classroom = {s1, s4}
print(f"班级人数(去重后): {len(classroom)}")
预期输出:
📚 小明:85分
📚 小红:92分
📚 小李:88分
True
班级人数(去重后): 1
解释:用类方法从字符串/字典创建对象,用 __str__ 让打印好看,用 __eq__ 和 __hash__ 实现按名字去重。
项目 2:CSV 批量导入学生数据(15 分钟)
需求:从一个 CSV 文件读取学生数据,批量创建对象,统计平均分,找出最高分学生。
先准备一个 students.csv 文件(注意是真实文件路径):
小明,85
小红,92
小李,88
小张,76
小王,95
完整代码:
import csv
from io import StringIO # 模拟文件,真实环境用 open()
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
@classmethod
def from_csv_line(cls, line):
"""从 CSV 行创建学生"""
name, score = line.strip().split(',')
return cls(name, int(score))
@staticmethod
def is_valid_score(score):
"""验证分数是否合法"""
return 0 <= score <= 100
def __str__(self):
status = "✅" if self.score >= 60 else "❌"
return f"{status} {self.name}:{self.score}分"
def __repr__(self):
return f"Student('{self.name}', {self.score})"
class StudentManager:
def __init__(self):
self.students = []
def load_from_csv(self, csv_content):
"""从 CSV 内容加载学生数据"""
reader = csv.reader(StringIO(csv_content))
for row in reader:
if len(row) != 2:
continue
name, score = row[0], int(row[1])
if Student.is_valid_score(score):
self.students.append(Student(name, score))
else:
print(f"⚠️ 跳过非法分数:{name}, {score}")
def get_average(self):
"""计算平均分"""
if not self.students:
return 0
total = sum(s.score for s in self.students)
return total / len(self.students)
def get_top_student(self):
"""获取最高分学生"""
return max(self.students, key=lambda s: s.score)
# 模拟 CSV 内容(真实环境用 open('students.csv').read())
csv_content = """小明,85
小红,92
小李,88
小张,76
小王,95
小赵,105"""
manager = StudentManager()
manager.load_from_csv(csv_content)
print("=== 学生列表 ===")
for s in manager.students:
print(s)
print(f"\n平均分:{manager.get_average():.1f}")
print(f"最高分:{manager.get_top_student()}")
预期输出:
⚠️ 跳过非法分数:小赵, 105
=== 学生列表 ===
✅ 小明:85分
✅ 小红:92分
✅ 小李:88分
✅ 小张:76分
✅ 小王:95分
平均分:87.2
最高分:✅ 小王:95分
解释:静态方法 is_valid_score 验证分数合法性,类方法 from_csv_line 批量创建对象。
项目 3:带数据持久化的待办清单(15 分钟)
需求:一个待办清单,支持添加、完成、查看,能保存到 JSON 文件,下次启动自动加载。
import json
from pathlib import Path
class TodoItem:
def __init__(self, title, completed=False):
self.title = title
self.completed = completed
def complete(self):
self.completed = True
def __str__(self):
status = "✅" if self.completed else "⬜"
return f"{status} {self.title}"
def __repr__(self):
return f"TodoItem('{self.title}', {self.completed})"
def to_dict(self):
return {"title": self.title, "completed": self.completed}
@classmethod
def from_dict(cls, data):
return cls(data['title'], data['completed'])
class TodoList:
def __init__(self, filename="todo.json"):
self.filename = filename
self.items = []
self.load()
def add(self, title):
self.items.append(TodoItem(title))
self.save()
def complete(self, index):
if 0 <= index < len(self.items):
self.items[index].complete()
self.save()
def show(self):
if not self.items:
print("📝 待办清单是空的!")
return
print("📝 待办清单:")
for i, item in enumerate(self.items):
print(f" {i+1}. {item}")
def save(self):
data = [item.to_dict() for item in self.items]
with open(self.filename, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False)
def load(self):
if Path(self.filename).exists():
with open(self.filename, 'r', encoding='utf-8') as f:
data = json.load(f)
self.items = [TodoItem.from_dict(d) for d in data]
# 演示(真实环境运行多次会持久化)
if __name__ == "__main__":
todo = TodoList("my_todo.json")
# 添加几个待办
todo.add("完成 Python 作业")
todo.add("给妈妈打电话")
todo.add("整理房间")
# 完成第二个
todo.complete(1)
# 展示
todo.show()
预期输出(首次运行):
📝 待办清单:
1. ⬜ 完成 Python 作业
2. ✅ 给妈妈打电话
3. ⬜ 整理房间
解释:用 __str__ 控制显示效果,用 to_dict/from_dict 实现 JSON 序列化。文件 my_todo.json 会在当前目录生成,下次运行自动恢复数据。
💪 进阶:常见坑 + 调试技巧
坑 1:__eq__ 和 __hash__ 不一致
# ❌ 错误示例
class BadStudent:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
# 忘记实现 __hash__,或 hash 和 eq 逻辑不一致
def __hash__(self):
return hash(self.name + "_suffix") # 跟 eq 比较的不一样!
# ✅ 正确示例
class GoodStudent:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
def __hash__(self):
return hash(self.name) # 必须跟 __eq__ 用相同的字段
原因:Python 规定「相等的对象必须有相等的哈希值」。违反这条会导致 set/dict 出错。
坑 2:静态方法不需要 self 但写了 self
# ❌ 错误示例
class MathTool:
@staticmethod
def bad_add(a, b):
return self.a + self.b # 静态方法里没有 self!
# ✅ 正确示例
class MathTool:
@staticmethod
def good_add(a, b):
return a + b # 直接用参数
坑 3:__str__ 返回了调试信息
# ❌ 错误示例
class Student:
def __str__(self):
return f"Student(name='{self.name}')" # 这是 __repr__ 的风格
# ✅ 正确示例
class Student:
def __str__(self):
return f"{self.name},分数{self.score}" # 给用户看的简洁信息
def __repr__(self):
return f"Student('{self.name}', {self.score})" # 给开发者看的详细信息
坑 4:类方法第一个参数不是 cls
# ❌ 错误示例
class Student:
@classmethod
def from_dict(cls, data):
return cls(data['name'], data['score'])
@classmethod
def wrong_method(self, data): # 类方法第一个参数必须是 cls,不是 self!
return self.from_dict(data)
# ✅ 正确示例
class Student:
@classmethod
def from_dict(cls, data):
return cls(data['name'], data['score'])
@classmethod
def from_list(cls, lst):
return cls(lst[0], lst[1]) # cls 才是正确的第一个参数
调试技巧:用 vars() 查看对象所有属性
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
s = Student("小明", 85)
print(vars(s)) # 输出:{'name': '小明', 'score': 85}
# 或者用 __dict__
print(s.__dict__) # 同上
场景:当你不确定对象里有什么数据时,vars() 或 obj.__dict__ 能快速看到所有实例属性。
✏️ 练习题
练习 1(2 分钟):类方法调用
- 输入:调用 Student.from_dict({"name": "小刚", "score": 78})
- 预期输出:小刚:78分(用 __str__ 格式)
- 提示:直接用项目 1 的 from_dict 类方法
练习 2(3 分钟):添加判断逻辑
- 输入:在项目 1 基础上,添加一个 is_passed() 方法,返回 True/False
- 预期输出:Student("小明", 85).is_passed() → True,Student("小红", 55).is_passed() → False
- 提示:60 分及格
练习 3(5 分钟):处理新数据格式
- 输入:"小明:85,小红:92,小李:88" 用冒号分隔的字符串
- 预期输出:打印三个学生的信息
- 提示:参考 from_line 的写法,用 : 分割而不是 ,
练习 4(8 分钟):组合项目 2 和 3
- 输入:用项目 2 的 CSV 加载功能加载学生,再用项目 3 的 __eq__ 去重
- 预期输出:相同姓名的学生只保留一个
- 提示:利用 __hash__ 和 __eq__ 实现 set 去重
练习 5(5 分钟):修复报错
- 输入:以下代码报错,找出原因并修复
class A:
def __init__(self, v):
self.v = v
def __eq__(self, other):
return self.v == other.v
a1 = A(1)
a2 = A(1)
print({a1, a2}) # 报错:unhashable type
- 预期输出:
{A(1)}(只有一个元素) - 提示:加了
__eq__就必须加__hash__
作业:做一个「个人账本工具」
- 需求描述:记录你的日常收入和支出,支持按月统计,显示结余
- 功能点:
1. 用类方法from_entry创建账目条目
2. 用__str__美化输出(显示「💰 收入 +100元」或「💸 支出 -50元」)
3. 用静态方法is_valid_amount验证金额(正数才合法)
4. 支持保存到 JSON 文件 - 加分项:
1. 按月份筛选账目
2. 用__repr__显示月度统计摘要 - 验收标准:能跑起来、能添加账目、能显示月度结余
- 提交方式:评论区贴代码
📚 总结
这一章学了三个让对象更好用的工具:
1. @classmethod —— 类的另类构造函数,批量创建对象超方便
2. @staticmethod —— 挂靠在类名下的工具函数,不依赖实例
3. 魔术方法 —— __str__/__repr__/__eq__/__hash__,让对象更聪明
延伸资源:
- 官方文档:Data Model - Python(第 3 章讲的就是这些)
- 书籍:《Python 编程:从入门到实践》第 9 章
- 视频:搜索「Python 魔术方法」有大量可视化教程
互动钩子:你在写代码时遇到过 __eq__ 和 __hash__ 不一致的坑吗?或者用过什么有趣的类方法/静态方法?评论区聊聊,老粉优先回复!
下章预告:学完了类方法、静态方法、魔术方法,是时候做一个完整的银行账户管理系统了。下一章我们会综合运用这 4 章的所有知识,从类设计到继承,从多态到这些方法,写出一个能存钱、取钱、转账、带利息计算的完整系统。敬请期待!

评论(0)