跳到主要内容
Python 程序设计 · 在线教材适用于 2026—2027 学年第一学期

第 5 章 · 让输入成为可检查的数据

字符串与报名文本清洗

第 4 章的 M1 能批量判定报名,但键盘输入不一定整齐:编号可能写成小写、字段前后带空格,也可能少一段或混入全角数字。本章学习先检查结构、再定位和清洗字段、最后校验格式,让程序既能规范允许的差异,也能明确拒绝不合格记录。

预计阅读
65 分钟
上机实践
90 分钟
前置知识
第 1—4 章
本章产出
个人报名文本清洗器

学完本章,你应该能够

  • 用长度、索引和切片定位字符串中的字符与片段,区分索引越界和切片越界。
  • 说明字符串不会原地修改,正确保存 strip()、upper() 等方法返回的新值。
  • 使用 count()、find() 定位两个分隔符,按规则切出三个字段。
  • 用遍历和成员判断检查编号后缀,不丢失前导零,也不放过全角数字。
  • 区分可自动清洗的差异与必须拒绝的格式错误,并给出第一项失败原因。
  • 独立完成清洗程序、八组测试与个人限时修改。

5.1 报名文本为什么要先清洗

先比较两条记录:陈晨|CL007|EV-0021 和 陈晨 | cl007 | ev-0021 。它们表示相同的三个字段,但直接比较整个字符串会得到不同结果。反过来,陈晨|CL0A7|EV-0021 含错误字符,不能仅靠“看起来差不多”就自动修成正确编号。

本章约定记录格式为 姓名|社团编号|活动编码,中间必须恰好有两个半角竖线。字段首尾空白可以去除,编号大小写可以统一;姓名清洗后不能空,社团编号是 CL 加三位半角数字,活动编码是 EV- 加四位半角数字。

阶段做什么例子
结构检查先确认恰有两个半角分隔符少一段或多一段立即报告结构错误
清洗只做约定允许的规范化 cl007 → CL007
校验检查姓名、长度、前缀和数字字符CL0A7 拒绝,不猜测原意
输出只对通过的记录给出标准文本陈晨|CL007|EV-0021
“格式有效”不等于“报名成功”

本章只确认文本符合教学用格式。是否本校学生、是否付足费用、是否还有名额,仍属于第 3、4 章的业务审批;不能因为文本通过就宣称报名成功。这套 CL、EV- 规则也不是学校真实编号标准。

5.2 字符串、长度与索引

编号 007 是文本,不是人数。若把 "007" 转成整数,就只剩 7,前导零这一格式信息会消失。本章始终把姓名和编号保持为字符串。len() 给出字符数;用方括号索引可取某个位置,首字符索引为 0,最后一个也可写作 -1。

string_index.py
code = "EV-0021"
print(len(code))
print(code[0])
print(code[2])
print(code[-1])
print("007", int("007"))
运行结果
7
E
-
1
007 7

这里 code 长度为 7,有效的正索引是 0 到 6。取 code[7] 会报 IndexError;把它作为运行前预测题即可,不要误当作可运行的正确示例。len() 是内置函数,不属于后面六个字符串方法。

首字符和最后字符不是整条规则

检查出第一个字符是 C,并不能证明 CL007 长度、前缀和后缀都正确。索引帮助观察局部;真正校验还要组合多项条件。

5.3 切片:从起点到终点之前

切片 text[start:stop] 取得从 start 开始、到 stop 之前的一段文本;右边界不包含在结果中。省略起点表示从开头取,省略终点表示取到末尾。我们稍后会用它提取字段和编号前后缀。

string_slice.py
code = "EV-0021"
print(code[:3])
print(code[3:])
print(code[3:5])
print(code[99:] == "")
运行结果
EV-
0021
00
True

code[:3] 取索引 0、1、2,因此是 EV-;code[3:] 保留了 0021 的两个前导零。单点索引越界会报错,而 code[99:] 返回空字符串,因此最后一行比较得到 True。

切片“不报错”不代表数据有效:CL 的 [2:] 也是空字符串。因此校验编号时还必须单独检查总长度。

5.4 方法返回新字符串:先清洗,再保存

字符串不能像可修改的格子那样在原位置改变某个字符。写 code[0] = "c" 会出错;strip()、upper()、lower() 等方法返回处理后的新字符串,不会自动替换原变量。需要后续使用时,必须把结果赋值给名称。

save_cleaned_text.py
raw = "  cl007  "
raw.strip()
print(f"原值:[{raw}]")
club_code = raw.strip()
club_code = club_code.upper()
print(f"新值:[{club_code}]")
print("Y".lower())
运行结果
原值:[  cl007  ]
新值:[CL007]
y

第一行 raw.strip() 的返回值没有保存,raw 仍含空格。后面先保存去除首尾空白的结果,再转大写。lower() 可把输入的 Y 变为 y;但这并不意味着本项目可以随意更改所有字段。

方法本章用途不会做的事
strip()去字段首尾空白不会删除姓名内部空格
upper()统一编号字母大小写不会补齐缺失数字
lower()理解反方向的大小写统一基础程序不把编号转小写

例如 " Li Ming ".strip() 得到 "Li Ming",姓名中间的空格必须保留。把所有空格都删掉会改变原始姓名,不符合本章清洗约定。

5.6 先检查结构,再定位并切出三个字段

要读出姓名、社团编号和活动编码,先确认记录中恰好有两个半角竖线。两个分隔位置确定后,三段切片的边界才有依据;不能把姓名长度写死,因为“陈晨”和“Li Ming”长度不同。

parse_three_fields.py
record = " 陈晨 | cl007 | ev-0021 "
if record.count("|") == 2:
    first_bar = record.find("|")
    second_bar = record.find("|", first_bar + 1)
    student_name = record[:first_bar].strip()
    club_code = record[first_bar + 1:second_bar].strip().upper()
    activity_code = record[second_bar + 1:].strip().upper()
    print(student_name)
    print(club_code)
    print(activity_code)
运行结果
陈晨
CL007
EV-0021

第一个字段在第一个竖线左边;第二个字段从第一个竖线后一位到第二个竖线之前;第三个字段从第二个竖线后一位到结尾。每个字段要分别 strip(),因为只清洗整条记录的两端,无法去掉竖线旁边的空白。

结构正确也不保证内容正确:陈晨||EV-0021 仍有两个竖线,但社团编号是空的。程序必须在解析之后继续校验字段。

5.7 遍历后缀,用成员判断校验字符

社团编号规则是“总长 5、前缀 CL、后三位都是半角数字”。字符串可以被 for 逐字符遍历;char not in "0123456789" 判断当前字符不属于允许集合。任何一位不合格,就把布尔标记改为 False,后续合法字符也不能把它改回 True。

check_club_digits.py
club_code = "CL0A7"
club_digits_ok = True
for char in club_code[2:]:
    if char not in "0123456789":
        club_digits_ok = False
    print(char, club_digits_ok)
print(len(club_code) == 5 and club_code[:2] == "CL" and club_digits_ok)
运行结果
0 True
A False
7 False
False

这就是课堂中的“坏字符标记”:读到 A 后,标记保持 False。用 in 可询问“在不在”,用 not in 可直接表达“不在”。本章限定半角 0123456789,所以全角 007 不能通过。

输入长度前缀后缀逐字符结论
CL0075 ✓CL ✓0、0、7 ✓有效
CL0A75 ✓CL ✓A 不在半角数字中无效
CL2 ✗CL ✓空后缀,循环零次无效

CL 的循环零次执行,标记仍是初始 True;因此不能只凭标记判定有效。活动编码同理,但总长应为 7、前缀为 EV-、后缀从索引 3 开始。

5.8 完成一次处理一条记录的清洗器

现在把“结构 → 定位 → 清洗 → 校验 → 输出”串成一份完整程序。基础版只接受两个半角竖线;姓名清洗后不能为空;两个编号分别检查长度、前缀和逐字符结果。保存为 text_cleaner.py 后,从第一行运行。

text_cleaner.py
# 第5章:一次清洗并校验一条报名记录
# 格式:姓名|社团编号|活动编码
raw_record = input("报名记录(姓名|社团编号|活动编码):")
record = raw_record.strip()

if record.count("|") != 2:
    print("校验失败:记录必须恰有两个半角竖线")
else:
    first_bar = record.find("|")
    second_bar = record.find("|", first_bar + 1)
    student_name = record[:first_bar].strip()
    club_code = record[first_bar + 1:second_bar].strip().upper()
    activity_code = record[second_bar + 1:].strip().upper()

    club_digits_ok = True
    for char in club_code[2:]:
        if char not in "0123456789":
            club_digits_ok = False

    activity_digits_ok = True
    for char in activity_code[3:]:
        if char not in "0123456789":
            activity_digits_ok = False

    if student_name == "":
        print("校验失败:姓名不能为空")
    elif len(club_code) != 5 or club_code[:2] != "CL" or not club_digits_ok:
        print("校验失败:社团编号须为CL加3位半角数字")
    elif len(activity_code) != 7 or activity_code[:3] != "EV-" or not activity_digits_ok:
        print("校验失败:活动编码须为EV-加4位半角数字")
    else:
        print(f"姓名:{student_name}")
        print(f"社团编号:{club_code}")
        print(f"活动编码:{activity_code}")
        print(f"标准记录:{student_name}|{club_code}|{activity_code}")
        print("校验通过:文本格式有效,尚未执行报名审批")
运行结果
报名记录(姓名|社团编号|活动编码):  陈晨 | cl007 | ev-0021  
姓名:陈晨
社团编号:CL007
活动编码:EV-0021
标准记录:陈晨|CL007|EV-0021
校验通过:文本格式有效,尚未执行报名审批

运行结果第一行中,提示后面的文本是示例键盘输入,不是源代码。点击“在线运行”只会复制代码并打开你指定的 W3Cschool 新标签页;仍需粘贴代码,再提供一条输入。下载文件与此完整示例一致。

  1. 1
    先守住结构

    若半角竖线不是恰好两个,立即只报结构错误,不查找和切片字段。

  2. 2
    按位置取字段

    两次 find() 找到真实分隔位置,再切片、清洗,保留姓名内部空格和编号前导零。

  3. 3
    逐字符记住错误

    两个后缀各有一个布尔标记,发现不合格字符后保持 False。

  4. 4
    只报第一项原因

    按姓名、社团编号、活动编码的顺序检查;全部通过才输出标准记录。

为什么不把错误文本继续“修好”

CL 07 的内部空格、CL07 的缺位、CL007 的全角数字,都不在本章允许自动清洗的范围。猜测并补正可能把另一条记录误当成有效记录,拒绝并说明原因更可信。

5.9 八组必测数据与错误诊所

先根据规则写预期,再逐条运行。表格里的竖线表示实际要键入的半角字符;H 的数字特意使用全角。失败组不应出现“标准记录”。

编号输入应看到的主要结果
A 陈晨 | cl007 | ev-0021 标准记录:陈晨|CL007|EV-0021
B |CL007|EV-0021姓名不能为空
C陈晨|CL007必须恰有两个半角竖线
D陈晨|CL007|EV-0021|备注必须恰有两个半角竖线
E陈晨|CL07|EV-0021社团编号须为 CL 加 3 位半角数字
F陈晨|CL007|EV-00A1活动编码须为 EV- 加 4 位半角数字
G Li Ming | cl007 | ev-0021标准记录:Li Ming|CL007|EV-0021
H陈晨|CL007|EV-0021社团编号须为 CL 加 3 位半角数字

从现象追查第一处错误

清洗后仍有空白,先看是否保存了 strip() 的返回值,以及是否对三个字段分别清洗;第二个竖线位置和第一个一样,检查第二次 find() 是否从 first_bar + 1 开始;CL 被放行,检查长度条件是否缺失;CL0A7 被放行,检查坏字符标记是否被下一位合法字符错误地重置。

排查顺序分隔符数量→两个位置→切片边界→返回值保存→长度与逐字符标记

再自己加入空串、全角竖线、空后缀和错误前缀等异常文本。它们应按契约得到失败原因,不能因为程序没有抛异常就算通过。本版只处理一条记录,不保存名单,也不处理文件或非预期运行时错误。

5.10 练习与个人任务

先预测,再看解释

  1. 索引与切片

    code = "CL007" 时,code[2:] 和 code.find("|") 分别是什么?

    查看答案

    分别是 "007" 和 -1。切片保留了前导零;找不到竖线时 find() 返回 -1。

  2. 方法返回值

    执行 name = " Li Ming "、name.strip() 后,name 是否已经改变?

    查看答案

    没有。要写 name = name.strip() 才让变量关联到新字符串;姓名内部空格仍保留。

  3. 读边界

    为什么 CL 的逐字符检查没有发现坏数字,却仍必须判为无效?

    查看答案

    后缀为空时循环零次,标记保持初始 True。总长度只有 2,不符合 5 位要求。

  4. 区分清洗与校验

    cl007 与 CL007 哪个可被基础版规范化?为什么?

    查看答案

    cl007 可转为 CL007;全角数字不属于约定的半角 0123456789,应拒绝而不是猜测转换。

第 5 周 · 个人机房任务

独立完成报名文本清洗器

每位学生自己编写、测试并解释一份程序。页面示例用于理解格式契约;个人作业还需留下独立的八组实际测试、至少一组自选错误文本和现场限时修改证据。同伴只能提供黑盒输入与反馈。

最低验收要求

  • 提交 w05_学号_text_cleaner.py,文件开头写个人信息与用途。
  • 输入一条记录,严格按“结构 → 姓名 → 社团编号 → 活动编码”的顺序给出唯一结果。
  • 保留姓名内部空格与编号前导零;只去字段首尾空白、统一编号大小写。
  • 两个编号同时检查长度、前缀与半角数字;失败时不输出标准记录。
  • 提交 A—H 八组“预期—实际—结论—修正后复测”记录,另测至少一组自选错误文本。
  • 限时个人修改:另存为 w05_学号_text_cleaner_check.py;原格式合法后再限制只接收 CL007,对 CL008 给出“本次只接收CL007社团”,而 CL07 仍先报格式错误。
  • 提交修改运行证据和 80—150 字错误复盘,能解释一项清洗规则与一项校验规则的区别。
下载完整演示代码

进阶可另存版本,用 replace() 接受全角竖线,并增加全角、半角混合输入测试;挑战可用第 4 章的循环批量处理多条记录。新增规则必须写明且与基础版分开,不能悄悄改变 A—H 的验收口径。

章末检查

  • 我能说明索引、切片各自的边界和越界表现。
  • 我会保存字符串方法的返回值,不把清洗误认为原地修改。
  • 我在切字段前检查分隔符数量,第二次 find() 从第一处后面开始。
  • 我能解释为什么逐字符标记还必须配合长度、前缀检查。
  • 我把“格式有效”和“报名审批通过”当作两件不同的事。

延伸阅读:廖雪峰《Python教程》在线 PDF中的“字符串和编码”“切片”“不可变对象”,以及菜鸟教程 Python 3 字符串。本章按 ClubHub 的记录格式重新组织例子,以课程主数据的知识边界为准。