AI 学院 · Python 入门线 · 节点 ⑤ / ⑨

面向对象入门

手把手教程:把"数据 + 行为"打包成一个个"人"(对象),让代码贴合真实世界的模型。约 5-6 个番茄,学完做出一个「学习记录类」才算过。

状态 进行中 前置 ④ 文件与异常 下一步 ⑥ 常用标准库

第 0 步 · 为什么需要"类"

你现在的程序里,数据是数据(字典、列表),操作是操作(函数)。当程序变大,你会希望把"学生"这个事物的数据和它该有的行为放在一起

一句话类 = 图纸(模板),对象 = 按图纸造出来的具体实例。一个 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.,共享数据放类属性并明确用 类名.属性

节点自测清单

全勾 → 节点 ⑤ 完成,进入节点 ⑥(常用标准库)。未勾 → 回看对应步骤。

推荐资料