AI Class 3 · Chapter 2-3:函数、异常、文件读写+JSON / JSONL 读写(小白版)

Class 3 · Chapter 2:函数、异常、文件读写(小白版)

预计 40 分钟|前置:Chapter 1|产物:03_functions.py


0. 本章目标

把上一章散着的代码变成可复用函数 ,并学会两件保命技能:

文件不存在不崩溃、除零不崩溃。


1. 运行

powershell 复制代码
chcp 65001 | Out-Null
$env:PYTHONUTF8="1"; $env:PYTHONIOENCODING="utf-8"
[Console]::OutputEncoding=[Text.Encoding]::UTF8
& "D:\miniconda3\envs\llm\python.exe" "D:\myAImodle\Class03\Chapter02\03_functions.py"

2. 预期输出

复制代码
=== 1. 函数 ===
行数: 3
不存在的文件: None

=== 2. 异常 ===
10 / 2 = 5.0
10 / 0 = None

=== 3. 文件读写 ===
内容:
第一行
第二行
第三行
字符数: 12

=== 4. 模块 ===
math.sqrt(16) = 4.0

3. 完整代码

python 复制代码
from pathlib import Path


def count_lines(path):
    """返回文件行数;文件不存在时返回 None。"""
    p = Path(path)
    if not p.exists():
        return None
    with p.open("r", encoding="utf-8") as f:
        return sum(1 for _ in f)


def safe_divide(a, b):
    """安全除法,除数为 0 时返回 None。"""
    try:
        return a / b
    except ZeroDivisionError:
        return None


def main():
    demo = Path(r"D:\myAImodle\Class03\Chapter02\demo.txt")
    demo.write_text("第一行\n第二行\n第三行\n", encoding="utf-8")

    print("=== 1. 函数 ===")
    print("行数:", count_lines(demo))
    print("不存在的文件:", count_lines(demo.with_name("nope.txt")))

    print("\n=== 2. 异常 ===")
    print("10 / 2 =", safe_divide(10, 2))
    print("10 / 0 =", safe_divide(10, 0))

    print("\n=== 3. 文件读写 ===")
    content = demo.read_text(encoding="utf-8")
    print("内容:")
    print(content, end="")
    print("字符数:", len(content))

    print("\n=== 4. 模块 ===")
    import math
    print("math.sqrt(16) =", math.sqrt(16))


if __name__ == "__main__":
    main()

4. 逐块拆解

第 1 块:定义函数

python 复制代码
def count_lines(path):
    """返回文件行数;文件不存在时返回 None。"""
    p = Path(path)
    if not p.exists():
        return None
    with p.open("r", encoding="utf-8") as f:
        return sum(1 for _ in f)
  • def 函数名(参数)::定义函数;函数体必须缩进。
  • 三引号字符串是 docstring ,写函数用途,help(count_lines) 能看到。
  • return:把结果交回去;执行到 return 函数立即结束。
  • Path(path):把字符串变成路径对象,方便判断和打开。
  • p.exists():文件/目录是否存在。
  • with p.open(...) as f::打开文件,离开 with 自动关闭 ,不用手动 close()。
  • encoding="utf-8":不写就可能按 GBK 读,中文出错。
  • sum(1 for _ in f):每读一行产生一个 1,最后求和 = 行数;_ 表示「这个值不用」。

第 2 块:异常处理

python 复制代码
def safe_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return None
  • try:尝试执行可能出错的代码。
  • except ZeroDivisionError:只捕获「除以 0」这一种错误。
  • 出错时执行 except 分支,程序不会崩溃。
  • 捕获范围要小,不要写裸 except: 把所有错误都吞掉。

第 3 块:写文件与读文件

python 复制代码
demo.write_text("第一行\n第二行\n第三行\n", encoding="utf-8")
content = demo.read_text(encoding="utf-8")
  • write_text:一次写入整个字符串,覆盖原内容。
  • \n:换行符。
  • read_text:一次读回整个文件。
  • 大文件(几 GB)不要用 read_text,要像第 1 块那样逐行读。

第 4 块:模块

python 复制代码
import math
print(math.sqrt(16))
  • 模块 = 一个 .py 文件或标准库。
  • import math 后,用 math.xxx 调用里面的函数。
  • 后面会用到 import json、import pandas as pd,都是同一套用法。

第 5 块:main 保护

python 复制代码
if __name__ == "__main__":
    main()
  • 直接运行本文件时,__name__ 是 "__main__",于是调用 main()。
  • 被别的文件 import 时,不会自动执行,方便复用函数。
  • 这是 Python 项目的标准写法,后面训练脚本都这么写。

5. 常见错误

现象 原因 解决
FileNotFoundError 路径写错/文件不存在 先 Path.exists() 判断
IndentationError 缩进不一致 统一 4 个空格
读取中文报 UnicodeDecodeError 没指定编码 加 encoding="utf-8"
ZeroDivisionError 除数为 0 用 try/except 或先判断
函数返回 None 却参与计算 忘了 return 检查每个分支都有返回值
文件被占用 没关闭文件 用 with open

6. 练习

  1. 写一个 count_chars(path),返回文件字符数。
  2. 把 safe_divide 改成除零时返回字符串 "不能除以 0"。
  3. 为什么推荐 with open 而不是 f = open(...)?

7. 验收清单

  • 能独立写出带参数和返回值的函数
  • 会用 try/except 捕获指定异常
  • 会用 with open(..., encoding="utf-8")
  • 知道 if __name__ == "__main__" 的作用

8. 下一章

Class 3 Chapter 3:JSON / JSONL------训练数据的标准格式。


附:练习答案

python 复制代码
def count_chars(path):
    p = Path(path)
    if not p.exists():
        return None
    return len(p.read_text(encoding="utf-8"))
  1. 把 return None 改成 return "不能除以 0"。
  2. with 会在代码块结束时自动关闭文件,即使中途报错也会关,避免文件被占用和资源泄漏。

Class 3 · Chapter 3:JSON / JSONL 读写(小白版)

预计 40 分钟|前置:Chapter 2|产物:200 条样例数据 datasets\sample\sft_sample.jsonl


0. 本章目标

掌握训练数据的标准格式 JSONL :一行一条 JSON,能写、能读、能统计。

后面所有数据采集、清洗、训练都围绕它。


1. JSON 和 JSONL 的区别

JSON(一个数组):

json 复制代码
[
  {"instruction": "问题1", "output": "回答1"},
  {"instruction": "问题2", "output": "回答2"}
]

JSONL(一行一条):

json 复制代码
{"instruction": "问题1", "output": "回答1"}
{"instruction": "问题2", "output": "回答2"}

为什么训练都用 JSONL:

优点 说明
可流式读取 不用一次把几十 GB 读进内存
可追加 新数据直接往文件末尾加一行
容错 坏一行只影响一行,不会整个文件解析失败
工具友好 HuggingFace、LLaMA-Factory 默认支持

2. 运行

powershell 复制代码
chcp 65001 | Out-Null
$env:PYTHONUTF8="1"; $env:PYTHONIOENCODING="utf-8"
[Console]::OutputEncoding=[Text.Encoding]::UTF8
& "D:\miniconda3\envs\llm\python.exe" "D:\myAImodle\Class03\Chapter03\04_jsonl.py"

3. 预期输出

复制代码
写入: D:\myAImodle\datasets\sample\sft_sample.jsonl
条数: 200
首个样例: {"instruction": "请解释显存的第0个要点", "input": "", "output": "太短"}
读回条数: 200
output 平均长度: 36.4
文件大小: 32445 字节

打开文件看一行:

powershell 复制代码
Get-Content D:\myAImodle\datasets\sample\sft_sample.jsonl -TotalCount 2 -Encoding UTF8

4. 完整代码

python 复制代码
import json
import random
from pathlib import Path

random.seed(42)

OUT_DIR = Path(r"D:\myAImodle\datasets\sample")
OUT_DIR.mkdir(parents=True, exist_ok=True)
OUT_FILE = OUT_DIR / "sft_sample.jsonl"

topics = ["微调", "LoRA", "量化", "数据清洗", "推理", "显存"]
rows = []
for i in range(200):
    topic = random.choice(topics)
    instruction = f"请解释{topic}的第{i % 20}个要点"
    if i % 17 == 0 and rows:
        instruction = rows[-1]["instruction"]
    output = f"{topic}是训练中的关键概念。这是第{i}条示例,联系电话13800138000。"
    if i % 23 == 0:
        output = "太短"
    rows.append({"instruction": instruction, "input": "", "output": output})

with OUT_FILE.open("w", encoding="utf-8") as f:
    for row in rows:
        f.write(json.dumps(row, ensure_ascii=False) + "\n")

print("写入:", OUT_FILE)
print("条数:", len(rows))
print("首个样例:", json.dumps(rows[0], ensure_ascii=False))

count = 0
total = 0
with OUT_FILE.open("r", encoding="utf-8") as f:
    for line in f:
        line = line.strip()
        if not line:
            continue
        obj = json.loads(line)
        count += 1
        total += len(obj["output"])

print("读回条数:", count)
print(f"output 平均长度: {total / count:.1f}")
print("文件大小:", OUT_FILE.stat().st_size, "字节")

5. 逐块拆解

第 1 块:固定随机种子

python 复制代码
random.seed(42)
  • 让「随机」每次运行结果相同,方便复现。
  • 42 只是习惯用法,任何整数都行。

第 2 块:准备目录

python 复制代码
OUT_DIR = Path(r"D:\myAImodle\datasets\sample")
OUT_DIR.mkdir(parents=True, exist_ok=True)
OUT_FILE = OUT_DIR / "sft_sample.jsonl"
  • mkdir(parents=True, exist_ok=True):父目录不存在就一起建,已存在也不报错。
  • OUT_DIR / "文件名":路径拼接。

第 3 块:造数据

python 复制代码
for i in range(200):
    topic = random.choice(topics)
    instruction = f"请解释{topic}的第{i % 20}个要点"
    if i % 17 == 0 and rows:
        instruction = rows[-1]["instruction"]
    output = f"{topic}是训练中的关键概念。这是第{i}条示例,联系电话13800138000。"
    if i % 23 == 0:
        output = "太短"
    rows.append({"instruction": instruction, "input": "", "output": output})
  • range(200):0~199。
  • random.choice(topics):从列表里随机取一个。
  • i % 20:取余,让问题有 20 种变化。
  • i % 17 == 0:每 17 条制造一条重复,故意留脏数据给 Chapter 7 清洗。
  • i % 23 == 0:每 23 条制造一条过短样本。
  • 每条是一个字典,字段固定为 instruction / input / output。

第 4 块:写 JSONL

python 复制代码
with OUT_FILE.open("w", encoding="utf-8") as f:
    for row in rows:
        f.write(json.dumps(row, ensure_ascii=False) + "\n")
  • open("w"):覆盖写入。
  • json.dumps(字典):字典 → JSON 字符串。
  • ensure_ascii=False:必须加 ,否则中文会变成 \u4e2d\u6587。
  • + "\n":每条一行,这是 JSONL 的关键。

第 5 块:按行读

python 复制代码
for line in f:
    line = line.strip()
    if not line:
        continue
    obj = json.loads(line)
  • 逐行读,内存占用恒定。
  • strip():去掉行尾换行和空白。
  • continue:跳过空行,避免 json.loads("") 报错。
  • json.loads(字符串):JSON 字符串 → 字典。

第 6 块:统计

python 复制代码
count += 1
total += len(obj["output"])
  • 条数和总长度累加。
  • 最后算平均长度,这是最简单的数据体检。

6. 常见错误

现象 原因 解决
JSONDecodeError 某行不是合法 JSON 打印行号,检查该行
中文变成 \uXXXX 没加 ensure_ascii=False 加上即可
文件开头有 BOM 字符 被记事本存成 UTF-8 BOM 用 Python 重写,或 utf-8-sig 读
读到的条数偏少 空行/最后一行没换行 strip() + 跳过空行
写文件覆盖了旧数据 用了 "w" 追加用 "a"
Windows 路径报错 用了普通字符串 用 r"..."

7. 练习

  1. 把 range(200) 改成 500,重新运行,条数是多少?
  2. 给每条数据加一个字段 "source": "demo",写进 JSONL。
  3. 如果把 ensure_ascii=False 去掉,文件内容会变成什么样?

8. 验收清单

  • 能说出 JSON 和 JSONL 的区别
  • 会写 json.dumps(..., ensure_ascii=False)
  • 会逐行读取并跳过空行
  • 能统计条数和平均长度

9. 下一章

Class 3 Chapter 4:用 pandas 做长度分布统计。


附:练习答案

  1. 500 条(重复和过短样本也一起变多)。
  2. 在字典里加 "source": "demo",其余不变。
  3. 中文会变成 \u8bf7\u89e3\u91ca... 这样的转义序列,程序仍能读,但人看不懂。
相关推荐
Zhao-Jichao24 分钟前
AI Prompting for Everyone 课程笔记 - P21 总结 Conclusion
人工智能·笔记
阿里云大数据AI技术24 分钟前
EMR Serverless StarRocks 全面支持 Paimon 2.0:构建多模态统一分析与检索
人工智能
电科_银尘29 分钟前
【深度学习项目实战·11】损失函数与评价指标:让模型输出、标签与结果真正对应
人工智能·深度学习
RanceGru1 小时前
AI嵌入式7——瑞芯微RK3588s+RK1828 NPU 部署VLM模型(Qwen3-VL-2B)
人工智能
阿里云大数据AI技术1 小时前
云栖实录 | 具身智能前沿对话
人工智能
imythu1 小时前
让 Claude Code 在家跑,我在外面用手机盯着:做一个开源工具踩过的几个坑
人工智能·github
小小张说故事1 小时前
Python 的 obj.x 到底按什么顺序找?拆开数据描述符、实例字典和 __getattr__ 那条优先级链
python
用户6602638008361 小时前
8GB 显存跑 7B 大模型:vLLM 消费级显卡极限实战白皮书
人工智能
躺柒1 小时前
读数据安全架构设计与实践20安全政策文件体系(上)
大数据·人工智能·安全·数据安全·安全架构·安全体系