引言
第五章到此收官。本篇综合测验严格以第 1 至 9 篇为范围:字符串方法(第 1 篇《字符串方法大全》)、切片(第 2 篇)、编码与 Unicode(第 3 篇)、正则基础(第 4 篇)、正则进阶与分组(第 5 篇)、re 模块实战(第 6 篇)、格式化语法(第 7 篇)、nginx 日志解析(第 8 篇)与文本清洗工具链(第 9 篇)。题目不引入第六章(列表、元组与字典)及以后的内容;题中出现的字典计数与内置 sorted 等构件,是第 8、9 篇实战中已经用过的极基础用法,不再单独讲授。
全篇分三个部分:10 道选择题按”记忆 → 理解 → 应用”的难度梯度排列,每题标注难度并附逐题解析;3 道编程题给出可独立运行的参考解答与实测输出;最后是一个综合任务——从混合文本中提取并统计邮箱域名,考察你把整章知识串起来解决真实问题的能力。建议先独立完成全部题目,再对照解析;做错的题要回到对应篇章重新理解原理,因为测验的意义是发现盲区,而不是分数。文中所有运行结果均基于本机 CPython 3.12 实测。
选择题(共 10 题,每题只有一个正确答案)
第 1 题:字符串方法的返回值【记忆】
print(' Python 基础 '.strip())
print('hello,world,python'.split(','))
输出是什么?
A. 'Python 基础' 和 ['hello', 'world', 'python']
B. 'Python 基础' 和 ['hello', 'world', 'python']
C. 'Python 基础' 和 ['hello,world,python']
D. 'Python 基础' 和 ['hello,world,python']
第 2 题:切片与步长【记忆】
s = 'Python编程'
print(s[:6])
print(s[::2])
输出是什么?
A. 'Python' 和 'Pto编'
B. 'Python' 和 'Pto'
C. 'Pytho' 和 'Pto编'
D. 'Pytho' 和 'Pto'
第 3 题:字符串与字节【理解】
text = '你好'
data = text.encode('utf-8')
print(len(data))
print(data.hex())
print(data.decode('utf-8'))
输出是什么?
A. 4、e4bda0e5a5bd、你好
B. 6、4f60597d、你好
C. 6、e4bda0e5a5bd、你好
D. 6、e4bda0e5a5bd、报错
第 4 题:字符类与量词【记忆】
import re
print(re.findall(r'\d+', '2026 年 8 月,销量 1234 件,库存 45'))
输出是什么?
A. ['2026', '8', '1234', '45']
B. ['2', '0', '2', '6', '8', '1', '2', '3', '4', '4', '5']
C. ['2026', '8', '1234', '45', '']
D. [2026, 8, 1234, 45]
第 5 题:分组捕获与非贪婪【理解】
import re
html = '<a>text</a><b>more</b>'
print(re.findall(r'<(\w+)>', html))
print(re.findall(r'<.+?>', html))
输出是什么?
A. ['a', 'a', 'b', 'b'] 和 ['<a>', '</a>', '<b>', '</b>']
B. ['a', 'b'] 和 ['<a>text</a><b>more</b>']
C. ['a', 'b'] 和 ['<a>', '</a>', '<b>', '</b>']
D. ['a', 'a', 'b', 'b'] 和 ['<a>', '<b>']
第 6 题:match、search 与 fullmatch【理解】
import re
p = re.compile(r'\d+')
print(bool(p.match('abc123')))
print(p.search('abc123').group())
print(bool(p.fullmatch('abc123')))
输出是什么?
A. False、123、False
B. False、123、True
C. True、123、False
D. 123、123、False
第 7 题:findall 与捕获组【理解】
import re
line = 'ip=127.0.0.1 time=12.5ms status=200'
print(re.findall(r'(\w+)=([\d.]+)', line))
输出是什么?
A. ['ip=127.0.0.1', 'time=12.5', 'status=200']
B. [('ip', '127.0.0.1'), ('time', '12.5'), ('status', '200')]
C. ['ip', 'time', 'status']
D. [('127.0.0.1', 'ip'), ('12.5', 'time'), ('200', 'status')]
第 8 题:格式说明符【记忆】
n = 255
print(f'{n:#x}')
print(f'{n:08b}')
print(f'{3.14159:.2f}')
输出是什么?
A. 0xff、11111111、3.14
B. ff、11111111、3.14
C. 0xff、255、3.1
D. 0xFF、11111111、3.14
第 9 题:解析日志行【应用】
import re
log = '192.168.1.7 - - [31/Aug/2026:09:15:00 +0800] "POST /api/login HTTP/1.1" 401 128'
m = re.search(r'"([A-Z]+) (\S+?) HTTP/1\.[01]" (\d{3})', log)
print(m.groups())
输出是什么?
A. ('POST', '/api/login HTTP/1.1', '401')
B. ('POST', '/api/login', '401')
C. ('POST', '/api/login', '128')
D. ('POST', '/api/login', 'HTTP/1.1')
第 10 题:千分位还原——一次替换的陷阱【应用】
import re
s = '成交额 1,2,3,456 元'
print(re.sub(r'(\d),(\d)', r'\1\2', s))
输出是什么?
A. 成交额 123456 元
B. 成交额 1,2,3,456 元(不变)
C. 成交额 12,3456 元
D. 报错:反向引用 \1 不存在
点击查看答案
答案:B A C A C B B D B C
第 1 题(B):strip() 只去除首尾空白,中间的连续空格原样保留,所以结果是 'Python 基础';split(',') 按逗号切分得到三个元素的列表。选 B。若写成 'Python 基础'(中间单空格),说明误以为 strip 会压缩内部空白。
第 2 题(A):'Python编程' 共 8 个字符(6 个 ASCII + 2 个汉字),[:6] 左闭右开取前 6 个字符得 'Python';[::2] 以步长 2 取索引 0、2、4、6 的字符,第 6 个索引是汉字”编”,得 'Pto编'。选 A。切片按”字符”计数,中文同样占一个索引——这是第 2 篇的关键结论。
第 3 题(C):UTF-8 编码下每个汉字占 3 字节,“你好”共 6 字节,十六进制为 e4bda0e5a5bd,decode('utf-8') 原样还原。选 C。注意 4f60597d 是”你好”的 Unicode 码点(code point)的十六进制——码点是字符的抽象编号,字节序列才是硬盘上真实存储的内容,两者不能混为一谈(第 3 篇)。
第 4 题(A):\d+ 匹配一个或多个数字,+ 是贪婪量词,把连续数字串整体吃掉;中文字符与标点不参与匹配,所以得到四个数字串。选 A。B 把 + 当成不存在的量词逐字符匹配;C 的结尾空串是 re.split 才会出现的行为,findall 不会;D 少了引号,findall 永远返回字符串列表。
第 5 题(C):\w 匹配字母、数字、下划线,不匹配 /,所以 <(\w+)> 只能匹配 <a> 与 <b>,且 findall 在有捕获组时返回组内容,得 ['a', 'b'];<.+?> 是非贪婪写法,从第一个 < 到最近的 >,把四个标签逐个匹配出来。选 C。这是第 5 篇”分组 + 非贪婪”的典型组合:一点都不能记成”开闭标签成对出现”。
第 6 题(B):match 必须从位置 0 匹配,'abc123' 以字母开头,返回 None;search 任意位置查找,命中 '123';fullmatch 要求整体恰好匹配,'abc123' 含字母,返回 None。三者输出 False、123、False,选 B。做日志解析时记住这个分工:行首匹配用 match、任意位置用 search、格式校验用 fullmatch。
第 7 题(B):模式含两个捕获组时,findall 返回”元组列表”,每个元组是各组的匹配内容;[\d.]+ 能同时容纳数字与点号,所以 127.0.0.1 和 12.5 都能完整匹配。选 B。这是第 6 篇最常见的踩坑点:不带分组返回字符串列表,带两个分组就变成元组列表,后续遍历的写法完全不同。
第 8 题(D):#x 输出带 0x 前缀的小写十六进制;08b 是 8 位宽、零填充的二进制;.2f 保留两位小数(四舍五入)。选 D。注意 # 是”前缀开关”,x 默认小写;想要大写前缀要写 #X——第 7 篇强调的是”说明符的每一段都有明确含义”,逐个字母拆开读。
第 9 题(B):三个捕获组分别抓到 HTTP 方法、请求路径与状态码。关键在 \S+? 的非贪婪:它会在第一个空格处停下,路径 /api/login 之后必须紧跟字面 HTTP/1.1",所以不会把 HTTP/1.1 吞进路径。401 之后、128 之前没有引号,第三个组正确捕获状态码。选 B。这正是第 8 篇解析 nginx 日志时”先看结构、再定模式”的思路。
第 10 题(C):一次 sub 从左到右处理不重叠的匹配:1,2 变 12,3,4 变 34,可中间的逗号被夹在数字中间留了下来,结果 12,3456。第 9 篇的 restore_thousands 之所以用 while 循环替换到”字符串不再变化”(不动点),就是为了消灭这种残留。选 C。B 是没发生替换的情况,D 不会发生——反向引用在匹配成功的前提下必然存在。
编程题(共 3 题,附可运行参考解答)
编程题 1:用切片判断回文
题目:实现 is_palindrome(text),判断一个字符串正读反读是否相同(忽略大小写)。用第 2 篇的切片写出最简版本,并分别测试 'abcba'、'上海自来水来自海上'、'Python'。
参考解答:
def is_palindrome(text):
text = text.lower()
return text == text[::-1]
for word in ['abcba', '上海自来水来自海上', 'Python']:
print(f'{word} -> {is_palindrome(word)}')
运行结果:
abcba -> True
上海自来水来自海上 -> True
Python -> False
关键点:text[::-1] 是”步长 -1 的切片”,把字符串完整倒序,这是回文判断的最短路;lower() 统一大小写后,'Abcba' 也能正确判为回文。中文同样适用,因为切片按字符索引工作,与语言无关。整段逻辑只有一行核心比较,剩下的两个知识来自第 1 篇(大小写变换)与第 7 篇(f-string 输出)。
编程题 2:统计网址的域名后缀
题目:给定一组混合了不同协议与路径的网址,用正则提取每个网址的主机名,按顶级后缀(com/net/org)分组计数,并按字母顺序输出,数字右对齐、后缀左对齐。
参考解答:
import re
urls = ['https://docs.python.org/3/', 'http://example.com/a',
'https://blog.example.com/p', 'ftp://files.example.net/x',
'http://example.org', 'https://example.com/about']
stats = {}
for url in urls:
host = re.search(r'://([^/]+)', url).group(1)
suffix = host.rsplit('.', 1)[-1] # rsplit:从右侧切分
stats[suffix] = stats.get(suffix, 0) + 1
for suffix, count in sorted(stats.items()):
print(f'{suffix:<5}{count:>3}')
运行结果:
com 3
net 1
org 2
关键点:r'://([^/]+)' 用 [^/]+ 堵住协议与路径、只留主机名(第 4 篇字符类 + 第 5 篇分组);rsplit('.', 1) 从右侧只切一次,example.com 与 blog.example.com 都能正确取出 com;stats.get(suffix, 0) + 1 是字典计数的最小惯用式;输出格式 {suffix:<5}{count:>3} 用了第 7 篇的左对齐与右对齐。全部落在第五章知识内,没有引入任何新库。
编程题 3:手工清洗一行 CSV
题目:不带 csv 模块(那是后续章节的内容),只用字符串方法把一行 CSV 文本 ' "张三", 25 , "上海" ' 解析成 ['张三', '25', '上海']:按逗号切分、去掉两侧空白、再剥掉引号。
参考解答:
def parse_csv_line(raw):
fields = raw.split(',')
return [field.strip().strip('"') for field in fields]
line = ' "张三", 25 , "上海" '
print(parse_csv_line(line))
运行结果:
['张三', '25', '上海']
关键点:三步各对应第 1 篇的一个方法——split(',') 切字段、strip() 去空格、strip('"') 剥引号;三者用列表推导式串成一行,是第 9 篇”清洗管线”思想的函数级缩影。注意 strip() 不传参数默认剥空白字符,而 strip('"') 传入字符集合参数后只剥引号,两者的第二个用法是字符串方法的常见隐藏点。真实工程项目里 CSV 有引号内逗号等复杂情况,要交给标准库 csv——这里的手写版只用于理解”清洗 = 方法组合”。
综合任务:从混合文本中提取并统计邮箱域名
任务描述:你收到一封会议通知的纯文本,里面混着多个邮箱地址(大小写不一、有重复)。请完成:用正则提取全部邮箱 → 统一转为小写 → 按 @ 后面的域名分组计数 → 按出现次数从多到少排序(次数相同的保持先出现者在前)→ 用格式化语法输出”域名 次数”报告。
考察点:邮箱正则(第 4、6 篇)、findall 与大小写规整(第 6 篇、第 9 篇)、字典计数与排序(第 8、9 篇实战用过的构件)、f-string 对齐输出(第 7 篇)。
参考实现:
import re
TEXT = '''会议通知:请联系 zhangsan@example.com 或 lisi@example.com 确认出席,抄送 wang.wu@example.net。
域名迁移:zhangsan@example.com 与 zhaoliu@example.org 是公司新邮箱,旧邮箱 zhaoliu@old-mail.org 停用。'''
emails = re.findall(r'[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}', TEXT)
domains = {}
for email in emails:
domain = email.split('@', 1)[1].lower()
domains[domain] = domains.get(domain, 0) + 1
print(f'共提取 {len(emails)} 个邮箱,涉及 {len(domains)} 个域名:')
for domain, count in sorted(domains.items(), key=lambda item: -item[1]):
print(f'{domain:<24}{count:>3} 封')
运行结果:
共提取 6 个邮箱,涉及 4 个域名:
example.com 3 封
example.net 1 封
example.org 1 封
old-mail.org 1 封
逐项核对:zhangsan@example.com 出现两次、lisi@example.com 一次,example.com 合计 3;wang.wu@example.net 的域名是 example.net 而不是 net——因为 split('@', 1)[1] 取的是整个域名部分;old-mail.org 里的连字符在 [A-Za-z0-9.-]+ 字符类中位于末尾,按字面匹配,提取正确。同一个任务,把邮箱模式换成第 8 篇的日志行模式,把 split('@')[1] 换成状态码分组,就是一台日志统计小工具——这就是迁移能力:模式相同,数据换掉即可。
扩展思考:把输入换成 nginx 日志文件,统计各状态码(200/401/404…)的出现次数,并输出占比最高的前三个;试试只靠本章知识能否完成(提示:re.findall + 字典 + sorted 的 key 就够了)。
学习自检清单
完成本章学习后,请逐项确认你能独立做到:
- 说出
strip、split、join、replace、find、startswith等常用字符串方法的返回类型,并知道strip()默认剥哪些字符。 - 用切片取子串、倒序、按步长取字符,并能解释越界与负索引的边界行为。
- 解释
str与bytes的关系,完成 UTF-8 的encode/decode,能说出”一个汉字 3 字节”并预判UnicodeDecodeError。 - 独立写出含字符类(
\d、\w、\s、[^...])与量词(*、+、?、{n,m})的正则模式。 - 使用分组
()、反向引用\1与非贪婪*?、+?解决实际问题。 - 区分
match、search、fullmatch、findall、sub的语义与返回值形态(字符串列表 vs 元组列表)。 - 用 f-string 的格式说明符完成对齐、填充、精度与进制输出(如
{n:#x}、{x:08b})。 - 不查资料,从 nginx 日志行中提取方法、路径、状态码与耗时(第 8 篇)。
- 独立搭建”分层清洗 → 提取 → 统计 → 结构化输出”的文本清洗管线(第 9 篇),并知道千分位需要循环还原。
- 独立完成本篇综合任务,并能把同一套模式迁移到”统计 IP、状态码”等场景。
如果某一项无法打勾,请回到对应篇章重新练习。第五章的核心思想是:文本处理 = 字符串方法做精确操作 + 正则做模式匹配 + 格式化做输出规整,三者缺一不可;初学阶段最容易忽略的是”返回值形态”——每个方法返回字符串、列表还是 None,决定了下一行代码怎么写。带着这套基本功,下一章进入第六章”列表、元组与字典”:你会发现字符串的切片、索引与统计思想会原样迁移到容器类型上,而第五章反复强调的”先看返回值、再写下一行”的调试习惯,将帮助你无痛跨过数据结构这道坎。测验不是终点,它只是你掌握程度的体检报告。