| Day 14 | 阶段二项目:学生信息管理系统 |

1 项目总览 —— 学生信息管理系统(阶段二收官)

是什么?为什么学?

今天是阶段二收官日,没有新知识点——重点是把 Day 8~13 学的零散知识组装成一个「真正能用的」控制台管理系统。前 13 天学的知识今天全部用上:函数组织代码、列表+字典存数据、文件读写做持久化、异常处理兜底。这个系统做完,你就具备做一个「能用的工具」的能力了。

阶段知识地图

Day 8   函数入门(def/参数/返回值/作用域) ──────┐
Day 9   函数进阶(*args/**kwargs/lambda/递归) ──┐│
Day 10  模块与包(import/标准库/自定义模块) ────┐││
Day 11  文件读写(open/with/w与a/编码) ────────┐│││
Day 12  CSV与JSON(csv模块/json四函数/持久化) ─┐││││
Day 13  异常处理(try/except/raise/自定义异常) ─┐│││││
                                             └─┴┴┴┴┴──→ 阶段项目:学生信息管理系统

一句话串法:函数让代码模块化 → 参数让函数通用 → 作用域管好变量 → 模块让代码拆分 → 文件读写让数据落盘 → CSV/JSON 让数据有结构 → 异常处理让程序扛得住。学生管理系统就是把这些零件按顺序组装起来:函数当骨架、列表字典当仓库、JSON 当保险柜、异常处理当安全气囊。

注意事项总结

1 项目拆解表(六大功能 → 知识点)

功能用到的知识点具体写法来自哪一天
数据模型列表 + 字典students = []{"name": ..., "age": ..., "score": ...}Day 6/7
添加学生函数、类型转换、异常处理int(input("年龄:"))except ValueErrorDay 8/2/13
查看全部循环、f-string 格式化for s in students:f"{s['name']:<8}..."Day 5/9
按姓名查找列表推导式、比较运算符[s for s in students if s["name"] == name]Day 6/7
删除学生列表推导式、切片赋值students[:] = [s for s in students if s["name"] != name]Day 6
保存数据函数、JSON 持久化json.dump(students, f, ensure_ascii=False, indent=2)Day 8/12
加载数据模块、文件判断、JSON、异常处理os.path.existsjson.loadexcept (json.JSONDecodeError, OSError)Day 10/12/13
主菜单while 循环、条件判断while True: + if/elif + breakDay 4/5

2 异常防护表(风险场景 → 知识点)

风险场景防护写法知识点
年龄 / 成绩输入非数字try: age = int(...) + except ValueErrorDay 13 异常处理
数据文件不存在(第一次运行)if os.path.exists(DATA_FILE):Day 10/11 模块与文件
数据文件被手动改坏except (json.JSONDecodeError, OSError)Day 12/13 JSON + 异常
保存后中文变 \uXXXXjson.dump(..., ensure_ascii=False)Day 12 JSON
函数内重新赋值全局列表global students 声明Day 8 作用域

易错点

  • 错误写法:一口气写完 100 行代码再运行(「憋大招」) → 问题:写了几十行没运行过一次,一运行全是错,不知道从哪改起 → 正确写法:三步走,每步都能运行、都能验证(见第 5 节)。
  • 错误写法:项目做不出来就怀疑是今天的问题 → 问题:往往是前面某天基础没打牢 → 正确写法:对照拆解表自查「这一步用到了哪个知识点」,回去翻那一天的总结。

记忆口诀:函数当骨架,字典当仓库;JSON 保险柜,异常安全气囊;拆解表对号入座,小步迭代不憋大招。

2 系统设计 —— 先想清楚再动手

是什么?为什么学?

动手写代码前,先画出功能地图——这就是程序员常说的「先设计再编码」。功能越多,提前画图越重要。

注意事项总结

1 设计四要素

  • 数据模型:学生列表 students,每个学生是一个字典:{"name": 姓名, "age": 年龄, "score": 成绩}
  • 数据持久化:保存到 students.json,启动时自动加载
  • 功能列表:添加、查看全部、按姓名查找、删除、保存、加载
  • 结构组织:每个功能一个函数,main() 里用循环 + 菜单调度

2 数据格式约定

import json
import os

DATA_FILE = "students.json"

# 一名学生 = 一个字典
# {"name": "张三", "age": 18, "score": 90}
# 所有学生 = 列表
# [{"name": "张三", ...}, {"name": "李四", ...}]

易错点

  • 错误写法:不设计直接开写 → 问题:写到一半发现结构不对,返工重来 → 正确写法:先画「数据模型 + 功能列表 + 结构组织」,再写代码。

记忆口诀:先画图再动工,数据模型定基调;功能列表一条条,函数调度不乱套。

3 数据模型与作用域 —— global 的坑

是什么?为什么学?

students 是全局列表,六个函数共享它。但「修改列表内容」和「重新赋值」对 global 的要求完全不同——这是本项目最容易踩的坑。

底层原理

students.append(...) 操作的是全局列表本身(往里面塞元素),Python 能认出这是在修改已有的全局对象,不需要 global。反过来,load_datastudents = json.load(f) 是让变量名指向一个全新的列表对象——不声明 global 的话,Python 会认为这是函数里的局部变量,赋值完函数结束就丢了,全局 students 还是空列表。

注意事项总结

1 修改列表内容 vs 重新赋值

students = []

def add_student():
    students.append({"name": "张三"})   # 修改列表内容:不需要 global

add_student()
print(students)

运行结果:

[{'name': '张三'}]

2 重新赋值必须 global

def load_data():
    global students               # 必须声明!
    students = json.load(f)       # 重新赋值:让变量指向全新对象

3 **删除用 students[:] = ... 而不是 students = ...**:切片整体替换保留同一个列表对象,所有引用这个列表的地方看到的都是新内容;students = ... 换新对象,旧对象上的修改就丢了。在多个函数共享同一个 students 的场景下,[:] 写法更稳。

易错点

  • 错误写法:load_datastudents = json.load(f) 忘了 global → 问题:报 UnboundLocalError,或全局 students 还是空列表 → 正确写法:函数开头写 global students
  • 错误写法:删除学生用 students = [...] → 问题:换了新对象,其它函数对旧对象的引用全部失效 → 正确写法:用 students[:] = [...] 原地替换。

记忆口诀:动旧对象不用 global,换新对象必须 global;删除用切片 [:],原地替换最稳当。

4 持久化与异常防护 —— JSON 保险柜 + 安全气囊

是什么?为什么学?

系统的「心脏」是保存和加载:save_datajson.dump 把整个学生列表存成 students.jsonload_datajson.load 读回来。程序重启后数据还在,靠的就是这一对函数。同时,异常处理让程序在非法输入、文件损坏时也不闪退。

注意事项总结

1 保存 / 加载的核心代码

def save_data():
    with open(DATA_FILE, "w", encoding="utf-8") as f:
        json.dump(students, f, ensure_ascii=False, indent=2)
    print(f"已保存 {len(students)} 名学生到 {DATA_FILE}")

def load_data():
    global students
    if os.path.exists(DATA_FILE):
        try:
            with open(DATA_FILE, "r", encoding="utf-8") as f:
                students = json.load(f)
            print(f"已从 {DATA_FILE} 加载 {len(students)} 名学生")
        except (json.JSONDecodeError, OSError) as e:
            print(f"加载文件失败({e}),使用空数据")
    else:
        print("没有数据文件,从空列表开始")

2 add_student 的数字校验

try:
    age = int(input("年龄:"))
    score = float(input("成绩:"))
except ValueError:
    print("年龄和成绩必须是数字,添加失败")
    return

int("abc") 会抛 ValueError,被 except ValueError 接住,打印友好提示——程序不崩溃,用户重新输入即可。

3 **为什么 except (json.JSONDecodeError, OSError) 而不是裸 except:**:精确捕获「文件损坏」和「读取失败」两类已知风险;裸 except: 会把所有异常(包括 Ctrl+C 的 KeyboardInterrupt)都吞掉,程序无法正常终止。

易错点

  • 错误写法:保存时漏了 ensure_ascii=False → 问题:中文变 \u5f20 转义码,文件没法读 → 正确写法:json.dump(..., ensure_ascii=False)
  • 错误写法:load_data 用裸 except: → 问题:Ctrl+C 也被吞掉,程序无法正常终止 → 正确写法:except (json.JSONDecodeError, OSError)
  • 错误写法:add_student 不加 try/except → 问题:用户年龄输入 “abc”,程序直接崩溃退出,之前录入的数据全白费 → 正确写法:try/except ValueError 捕获并提示。

记忆口诀:json.dump 存、json.load 取,重启不丢靠闭环;非法输入加防护,文件损坏不闪退。

5 动手实践 —— 完整的学生信息管理系统

下面是整个系统,一个文件直接运行。建议先通读一遍,再照着敲一遍加深印象:

# student_manager.py —— 学生信息管理系统
import json
import os

DATA_FILE = "students.json"
students = []   # 全局数据:所有学生(每个学生是一个字典)


def load_data():
    """从文件加载学生数据,文件不存在则保持空列表"""
    global students
    if os.path.exists(DATA_FILE):
        try:
            with open(DATA_FILE, "r", encoding="utf-8") as f:
                students = json.load(f)
            print(f"已从 {DATA_FILE} 加载 {len(students)} 名学生")
        except (json.JSONDecodeError, OSError) as e:
            print(f"加载文件失败({e}),使用空数据")
    else:
        print("没有数据文件,从空列表开始")


def save_data():
    """把学生数据保存到文件"""
    with open(DATA_FILE, "w", encoding="utf-8") as f:
        json.dump(students, f, ensure_ascii=False, indent=2)
    print(f"已保存 {len(students)} 名学生到 {DATA_FILE}")


def add_student():
    """添加一名学生"""
    name = input("姓名:").strip()
    if not name:
        print("姓名不能为空")
        return
    try:
        age = int(input("年龄:"))
        score = float(input("成绩:"))
    except ValueError:
        print("年龄和成绩必须是数字,添加失败")
        return
    students.append({"name": name, "age": age, "score": score})
    print(f"已添加:{name}")


def show_all():
    """查看全部学生"""
    if not students:
        print("暂无学生数据")
        return
    print(f"{'姓名':<8}{'年龄':<6}{'成绩':<8}")
    for s in students:
        print(f"{s['name']:<8}{s['age']:<6}{s['score']:<8}")
    print(f"共 {len(students)} 人")


def find_student():
    """按姓名查找学生"""
    name = input("请输入要查找的姓名:").strip()
    found = [s for s in students if s["name"] == name]
    if not found:
        print(f"没有找到名为 {name} 的学生")
        return
    for s in found:
        print(f"姓名:{s['name']},年龄:{s['age']},成绩:{s['score']}")


def delete_student():
    """按姓名删除学生(同名全部删除)"""
    name = input("请输入要删除的姓名:").strip()
    before = len(students)
    students[:] = [s for s in students if s["name"] != name]
    removed = before - len(students)
    if removed:
        print(f"已删除 {removed} 名 {name}")
    else:
        print(f"没有找到名为 {name} 的学生")


def main():
    """主菜单"""
    load_data()
    menu = (
        "\n===== 学生信息管理系统 =====\n"
        "1. 添加学生\n"
        "2. 查看全部\n"
        "3. 按姓名查找\n"
        "4. 删除学生\n"
        "5. 保存到文件\n"
        "6. 从文件加载\n"
        "0. 退出\n"
        "请选择:"
    )
    while True:
        choice = input(menu)
        if choice == "1":
            add_student()
        elif choice == "2":
            show_all()
        elif choice == "3":
            find_student()
        elif choice == "4":
            delete_student()
        elif choice == "5":
            save_data()
        elif choice == "6":
            load_data()
        elif choice == "0":
            save_data()   # 退出前自动保存,避免数据丢失
            print("再见")
            break
        else:
            print("无效选择,请输入 0-6")


if __name__ == "__main__":
    main()

运行效果:

没有数据文件,从空列表开始

===== 学生信息管理系统 =====
1. 添加学生
2. 查看全部
3. 按姓名查找
4. 删除学生
5. 保存到文件
6. 从文件加载
0. 退出
请选择:1
姓名:张三
年龄:18
成绩:90
已添加:张三
请选择:2
姓名      年龄    成绩
张三      18    90.0
共 2 人
请选择:0
已保存 2 名学生到 students.json
再见

第二次运行程序,会自动加载 students.json,刚才的学生还在。

值得注意的细节students[:] = ... 用切片整体替换;删除功能会删掉所有同名学生;退出时自动保存 + 提供手动保存双保险;load_data 捕获 JSON 损坏不崩溃;用 if __name__ == "__main__" 保护入口,之后可以被其它模块导入复用。

6 开发流程 —— 三步搭起你的系统

是什么?为什么学?

不要一口气写完 100 行代码——新手最常见的失败方式就是「憋大招」:写了几十行没运行过一次,一运行全是错,根本不知道从哪改起。正确做法是三步走,每步都能运行、都能验证

注意事项总结

1 第 1 步:搭骨架——先让菜单跑起来

def add_student():
    pass

def show_all():
    pass

def main():
    while True:
        choice = input("请选择(0 退出):")
        if choice == "0":
            break
        elif choice == "1":
            add_student()
        elif choice == "2":
            show_all()
        else:
            print("无效选择")

if __name__ == "__main__":
    main()

pass 是「占位语句」——函数体暂时空着也能通过语法检查。这一步的意义:先把「程序的形状」立起来,错误范围最小。

2 第 2 步:填空腹——先做「添加」和「查看」:把 add_studentshow_allpass 换成真实逻辑,跑一遍:能添加学生、能列表展示。做完这两步,系统已经有「核心价值」了。

3 第 3 步:加持久化和防护——保存/加载 + 异常处理:最后补上 save_data / load_data / find_student / delete_student,再给输入加上 try / except ValueError。这步是「锦上添花 + 加固」,即使出错也不影响前两步已经验证过的功能。

易错点

  • 错误写法:一次写完所有功能再运行 → 问题:出错范围大,难以定位 → 正确写法:每完成一步就跑一次、验证一次——出问题范围小、好定位。
  • 错误写法:跳过骨架直接写功能 → 问题:菜单逻辑和功能逻辑混在一起 → 正确写法:先让菜单循环跑通(用 pass 占位),再逐个填空。

记忆口诀:先骨架后填空,再加固;每步都运行,小步快迭代。

今日总结

阶段知识地图:函数 → 参数/作用域 → 模块 → 文件读写 → CSV/JSON → 异常处理,一环扣一环 系统设计先行:先画「数据模型 + 功能列表 + 结构组织」,再动手写代码 数据模型:一个列表存所有学生,一个字典(name / age / score)存一名学生 函数化:六个功能每个一个函数,main() 只做菜单调度,代码像说明书 作用域细节:students.append(...) 不用 global,students = ... 必须 global 持久化闭环:json.dump 保存 + json.load 加载,重启程序数据还在 异常防护:输入非数字(except ValueError)、文件损坏(except (JSONDecodeError, OSError))都不闪退 项目拆解能力:能对照「功能 → 知识点」表格,说清每个功能用到了哪几天的知识 开发流程:三步走(骨架 → 填空腹 → 加固),每步都能运行验证 独立完成学生信息管理系统,并测试非法输入、文件损坏等边界场景

报错原因修复
json.JSONDecodeErrorstudents.json 被手动改坏或格式不对删除损坏文件重新开始,或在 load_data 中捕获
UnboundLocalError函数内给全局列表赋值却忘了 global 声明students[:] = ... 修改,或在函数开头声明 global
ValueError: invalid literal for int()年龄输入了非数字参考 add_studenttry / except 捕获
KeyError: 'score'字典里没有这个键(比如旧数据格式不同)打印 students 检查每项的键是否一致
保存后中文变 \uXXXX写 JSON 时漏了 ensure_ascii=False保存时加上 ensure_ascii=False
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇