第 0 步 · 为什么需要"类"
你现在的程序里,数据是数据(字典、列表),操作是操作(函数)。当程序变大,你会希望把"学生"这个事物的数据和它该有的行为放在一起:
- 面向过程:
student = {"name": ..., "score": ...}+def add_score(student, ...)—— 数据和函数是两拨人。 - 面向对象:
student.add_score(...)—— 数据和行为打包,student自己会"做事"。
一句话:类 = 图纸(模板),对象 = 按图纸造出来的具体实例。一个
Student 类可以造出无数个学生对象,每个自带自己的数据。第 1 步 · 定义类与创建对象
class Student:
def __init__(self, name, age):
"""构造方法:创建对象时自动调用,self 指向这个新对象"""
self.name = name # 实例属性:每个对象自己的数据
self.age = age
self.scores = {}
def add_score(self, subject, score):
"""对象的行为(方法):第一个参数永远是 self"""
self.scores[subject] = score
def average(self):
if not self.scores:
return 0
return sum(self.scores.values()) / len(self.scores)
# 创建两个学生(两个独立对象)
s1 = Student("丢丢", 6)
s2 = Student("Riemann", 30)
s1.add_score("数学", 92)
s1.add_score("英语", 88)
print(s1.name, s1.average()) # 丢丢 90.0
print(s2.name, s2.average()) # Riemann 0(s2 没加过成绩)
self 是什么?:调用
s1.add_score("数学", 92) 时,Python 自动把 s1 传给 self。所以 self 就是"当前这个对象自己"。方法定义里第一个参数写 self 是铁律。第 2 步 · __init__ 与 __str__(两个最重要的魔法方法)
名字带 __ 的方法叫 dunder(双下划线)方法,Python 在特定时机自动调用:
class Student:
def __init__(self, name, age): # 创建时自动执行
self.name = name
self.age = age
def __str__(self): # print(对象) 时自动调用
return f"学生 {self.name},{self.age} 岁"
def __repr__(self): # 调试时显示的表示
return f"Student({self.name!r}, {self.age})"
s = Student("丢丢", 6)
print(s) # 学生 丢丢,6 岁(用了 __str__)
先只记
__init__ 和 __str__。__str__ 让对象打印出来可读——调试时极其好用。第 3 步 · 继承(子类复用父类)
class Person:
def __init__(self, name):
self.name = name
def introduce(self):
return f"我是 {self.name}"
class Student(Person): # 继承 Person
def __init__(self, name, school):
super().__init__(name) # 调用父类的构造(复用)
self.school = school
def introduce(self): # 覆盖 override:改写父类方法
return f"{super().introduce()},在 {self.school} 上学"
s = Student("丢丢", "实验小学")
print(s.introduce()) # 我是 丢丢,在 实验小学 上学
继承别滥用:只有当子类"是一种"父类(学生是一种人)才继承。为了省几行代码而继承,会让程序变复杂。80% 的场景组合(对象里有对象)比继承更清晰。
第 4 步 · 类属性 vs 实例属性
class Course:
count = 0 # 类属性:所有实例共享(整个类只有一份)
def __init__(self, title):
self.title = title # 实例属性:每个对象各一份
Course.count += 1 # 每建一个实例,计数器 +1
c1 = Course("Python")
c2 = Course("AI")
print(Course.count) # 2(记住"总共建了几个")
第 5 步 · 实战项目:学习记录类(融合前四节点)
需求:一个 StudyLog 类——记录每天学了什么(学科 + 番茄数),能加记录、算总番茄、算各科占比、导出 JSON。把④的文件读写也装进来。
Step 1:类的设计
import json
from datetime import datetime
class StudyLog:
def __init__(self, owner, file="study.json"):
self.owner = owner
self.file = file
self.records = [] # [{"date":..., "subject":..., "pomodoros": n}]
def add(self, subject, pomodoros):
"""加一条学习记录,自动记日期"""
self.records.append({"date": datetime.now().strftime("%Y-%m-%d"),
"subject": subject, "pomodoros": pomodoros})
def total(self):
return sum(r["pomodoros"] for r in self.records)
def by_subject(self):
"""各科番茄统计:{学科: 总番茄}"""
result = {}
for r in self.records:
result[r["subject"]] = result.get(r["subject"], 0) + r["pomodoros"]
return result
def save(self):
with open(self.file, "w", encoding="utf-8") as f:
json.dump({"owner": self.owner, "records": self.records},
f, ensure_ascii=False, indent=2)
def __str__(self):
return f"{self.owner} 共 {len(self.records)} 条记录,{self.total()} 番茄"
Step 2:使用它
log = StudyLog("Riemann")
log.add("Python", 4)
log.add("英语", 2)
print(log) # Riemann 共 2 条记录,6 番茄(__str__)
print(log.by_subject()) # {'Python': 4, '英语': 2}
log.save() # study.json 落盘
面向对象的回报在这时体现:数据(records)和行为(add/total/save)长在一起,调用方只关心"日志能做什么",不关心内部怎么存。加需求(比如按周统计)只改类内部,外部代码不动。
第 6 步 · 报错手册(本节点新增)
1. TypeError: __init__() takes 2 positional arguments but 3 were given —— 创建对象时参数个数不对。修法:数 __init__ 的参数(注意 self 自动传)。
2. AttributeError: 'Student' object has no attribute 'score' —— 访问了没定义的属性(可能是 __init__ 里忘了赋值)。修法:检查 __init__ 里有没有
self.xxx = ...。3. 忘写 self —— 方法里访问属性写成
name 而不是 self.name → NameError。修法:类内所有属性访问都带 self.。4. 改错了对象的属性 —— 两个对象互相影响(把类属性当实例属性用了)。修法:实例数据放
self.,共享数据放类属性并明确用 类名.属性。节点自测清单
- 能说清"类"和"对象"的区别,并各举一例
- 徒手写出:带 __init__、一个方法、一个类属性的类
- 能解释 self 的作用,以及方法为什么第一个参数是 self
- 徒手写出:继承 + super() 复用父类构造
- 能独立完成 StudyLog 类(含 save/__str__)
- 遇到 AttributeError 能快速定位(__init__ 赋值 vs 访问)
全勾 → 节点 ⑤ 完成,进入节点 ⑥(常用标准库)。未勾 → 回看对应步骤。