Python考试题库整理与自动组卷刷题实践
2026/9/19 2:24:19 网站建设 项目流程

简介:这是一份面向Python初学者的综合练习题库,以试卷形式整理了填空题、选择题、程序改错题、程序填空题和阅读程序题等常见题型,覆盖基础语法、数据类型、字符串与序列操作、控制结构、函数与模块、异常处理、文件输入输出等核心知识点。题目贴近教学与考试场景,且附有参考答案,适合高校学生、备考者及自学者用于阶段自测与考前强化。压缩包内包含1个PDF文件,共25页,大小约1.07MB,便于打印或电子阅读。目前已有706人学习下载。内容包含字符串切片、字典创建、变量命名、is与==身份比较等易错考点,同时配合程序改错与阅读题,帮助巩固代码阅读与调试能力。整体难度适中,由浅入深,既可用于检验基础掌握情况,也可作为期末复习或Python等级考试前的实战冲刺材料。

1. Python考试试题整理:先定考点,再谈整理

从网上下载一份「Python综合练习题题目整理附答案」的PDF,多数人的处理方式是打印出来,对完答案就放下,或者存进网盘吃灰。问题不在那25页题目本身,而在整理方式:按页码罗列的练习题只有题目和答案两层信息,没有章节、难度、考察点和解析,刷完记住的是一份PDF,不是Python。真正能反复使用的题目整理,是把知识点拆成骨架,让每道题挂到骨架上去,再用脚本完成抽题、打乱、判分和轮询。下面这套做法就从考点拆解开始,一直讲到把题库变成终端里的自测工具。

2. 用考点树给Python练习题分门别类,答案和解析才有地方安放

2.1 建议先建一张九类考点表

整理题目的第一件事不是翻译题干,而是定分类维度。因为后面要按章节抽题、按难度给分、按错误率排序,都需要先有元数据。我一般先把考点收敛成九类,再把每道题打上「章节」和「考点」两个标签。

分类常见考点典型题面信号高频误判
基础语法变量、类型转换、abs()、输入输出、缩进规则“下列哪个变量名合法”“abs(-3.7)的结果”把print返回值当函数返回值
内置数据结构列表、字典、元组、集合的增删查改与效率“以下哪个操作会原地修改列表”可变对象当函数参数时的共享引用
流程控制if/for/while、循环的else、异常捕获“以下循环结束后i的值是”break和continue后续代码依旧执行
函数与高级特性默认参数、闭包、lambda、装饰器、生成器“__name__等于什么”“yield和return的区别”默认参数是可变对象时共享状态
面向对象继承、多态、super()、魔法方法“以下哪个方法会被len()触发”类变量与实例变量读写顺序
文件与模块open、with、json、os.glob、模块导入机制“readline循环读取大文件”with块外再访问文件句柄
常用库与数据处理requests、re、pandas、matplotlib“用pandas筛选某列大于阈值”inplace=True后忘记重新赋值
并发与协程多进程、多线程、asyncio、GIL“以下哪个适合CPU密集任务”把多线程当作多核并行
算法与经典题排序、递归、01背包、动态规划“01背包问题的时间复杂度”二维DP数组更新顺序错误

这张表也决定了后续「附答案」的长度和密度。基础语法类题目五到八行就能讲透,算法类题目光有解析不够,必须补状态转移方程和一组样例变化。

2.2 用题面动词给题目自动分堆

老试卷里出题人的习惯相对固定,通过几个正则就可以把题目先分到粗分类里。

import re patterns = { "运行结果": [r"运行结果", r"输出为", r"结果是", r"打印"], "语法判断": [r"下列.*合法", r"是否有语法错误", r"哪.*正确", r"哪.*错误"], "概念填空": [r"___", r"____", r"填写", r"则\s*$"], "程序设计": [r"写一个", r"实现", r"完成", r"请编程"], } def rough_classify(stem: str) -> str: for category, keywords in patterns.items(): for kw in keywords: if re.search(kw, stem): return category return "其他"

rough_classify只看题干动词,不看参考答案。参数要点:re.search只要在题干任意位置命中关键词就会返回,所以「运行结果」的优先级要高于「哪.*正确」,避免一道题同时命中多个规则时先落到「语法判断」。

2.3 每道题至少带四样东西

分类只是起点。一道可以被长期复用的题目,光有题面和答案远远不够。我整理一批题时,会要求自己给每条题目补齐四个字段:答案、解析、难度、考察点。难度建议用 1 到 5 的整数,1 是送分题,5 是设计题或综合题。下面是一个推荐的最小 JSON 结构:

{ "id": 17, "chapter": "函数与高级特性", "tags": ["默认参数", "可变对象"], "type": "choice", "difficulty": 2, "stem": "下面代码执行后, result 的值是?\n\ndef f(x, lst=[]):\n lst.append(x)\n return lst\n\nprint(f(1), f(2))", "options": ["[1] [1, 2]", "[1] [2]", "[1, 2] [1, 2]", "报错"], "answer": "A", "analysis": "默认参数 lst 在函数定义时只创建一次, 两次调用共享同一个列表, 所以第二次输出 [1, 2]。" }

字段命名要稳定,因为第4章的组卷脚本直接读chapterdifficultyoptionsanswer四个键。tags用来做精细筛选,比如「可变对象」「GIL」「列表推导」这类细粒度知识点,光靠章节筛选不出来。

2.4 答案与解析的两种写法

给「附答案」写解析,最难的不是讲清正确路径,而是讲清错题为什么错。我通常用两行式解析:第一行给出结论和运行结果,第二行给一个反例。上面那道题的反例是“如果改成lst=None,再在函数里判断并新建列表,输出就变成[1] [2]”。反例只贴关键代码,不贴整个程序,省版面而且不打断刷题节奏。同一考点的错误率如果偏高,就优先补变式题。整理那 25 页 PDF 时,遇到考列表推导式和生成器区别的题,至少补一题“下列哪个在遍历后仍然可以继续迭代”,让两道题形成对仗。

3. 用Python从PDF抽取题目,把25页文档变成JSON题库

3.1 先判断PDF有没有文本层

常见的整理题PDF有两种:文字版和扫描版。文字版可以直接用pdfplumber按页提取;扫描版则需要额外接OCR,步骤会多一截。判断方法很简单:用page.extract_text()返回的字符串是否为空。

import pdfplumber with pdfplumber.open("python_题库.pdf") as pdf: first_page = pdf.pages[0] text = first_page.extract_text() or "" print(text[:200])

如果输出里能看到完整中文题干,说明有文本层,走下面的流程。如果输出是空白或者肉眼可辨的乱码,说明页面是扫描后嵌入的图片,这时不要在文本层上浪费时间,直接按页切成图片走OCR。

3.2 抽取文本并按题号切题

题干部分通常是1.1、1.开头,选项是 A/B/C/D 开头,答案常见「【答案】B」「参考答案:B」「答案:AB」三种写法。

import re import json import pdfplumber from pathlib import Path def extract_full_text(pdf_path: str) -> str: pages_text = [] with pdfplumber.open(pdf_path) as pdf: for page in pdf.pages: page_text = page.extract_text() if page_text: pages_text.append(page_text) return "\n".join(pages_text) def parse_questions(text: str): # 兼容 1. 1、 1. 三种题号写法 parts = re.split(r"(?m)^\s*(\d{1,3})[.、.]\s*", text) questions = [] # split 的结果是 [前言, 题号, 题干, 题号, 题干, ...] for i in range(1, len(parts), 2): questions.append({ "no": int(parts[i]), "raw": parts[i + 1].strip() }) return questions if __name__ == "__main__": raw_text = extract_full_text("python_练习题.pdf") qs = parse_questions(raw_text) print(f"共切出 {len(qs)} 题")

re.split的第一个参数(?m)表示多行模式,^\s*(\d{1,3})[.、.]\s*匹配行首的题号。注意这里先不解析答案,因为PDF排版经常把答案单独放一页,混在一起切题会把答案页也切成一道题。实际处理时我通常先定位「答案」或「参考答案」关键字,把正文和答案区拆成两个文本段,再分别走切题逻辑。

3.3 选项和答案要分开解析

切完题之后接着提取选项和答案。选项行有两个特征:行首是 A 到 D 的字母,字母后跟.

def parse_options(raw: str): options = [] for line in raw.splitlines(): line = line.strip() m = re.match(r"([A-D])[.、.]\s*(.*)$", line) if m: options.append(m.group(2).strip()) return options def parse_answer(raw: str) -> str: # 兼容【答案】B / 参考答案:B / 答案:AB m = re.search(r"[【\[]?\s*(?:答案|参考答案|Answer)\s*[】\]]?\s*[::]?\s*([A-D]+)", raw) return m.group(1) if m else ""

parse_options是按行匹配,所以选项行必须独立成行;如果PDF把两个选项挤在同一行,先用str.replace("B.", "\nB.")把行断开。parse_answer只提取连续的大写字母 A-D,多选题会得到"ACD"这样的字符串,后面判分时按集合比较更稳。

3.4 清洗后落盘成 JSON

抽取之后不要直接存文本,而是按第2章的字段结构落盘。批量处理时常见脏数据包括选项里混入页码、题干末尾残留换行符、答案区被识别成「答案B」、重复题号。我一般加两步收尾:按no去重,再把题干里的连续空白压成单个空格。

def clean_and_dump(qs: list, out_path: str) -> None: seen = set() cleaned = [] for item in qs: if item["no"] in seen: continue seen.add(item["no"]) cleaned.append({ "id": item["no"], "stem": re.sub(r"\s+", " ", item["raw"]).strip(), "answer": parse_answer(item["raw"]), "options": parse_options(item["raw"]), }) Path(out_path).write_text( json.dumps(cleaned, ensure_ascii=False, indent=2), encoding="utf-8" )

ensure_ascii=False保证汉字以明文写入,indent=2方便人眼检查。落盘后打开 JSON 抽看二十条,重点看 answer 是否为空、options 是否不足4个、stem 是否残缺,这三类问题分别对应三种根因。

现象根因处理方式
answer 全空答案区在PDF末尾单独成页先按「答案」关键字切分文档
options 少于4个两个选项被压缩到同一行用正则把行内选项拆开
题干只有一半分页符切断题干整篇拼接后再按题号切分

提示:切题后立刻输出统计信息,比如“共切出 87 题,其中答案缺失 3 题”,比人工逐行翻PDF高效得多。

4. 自动组卷:抽题比例、选项乱序与答案重标

4.1 用一份 plan 控制试卷构成

题目数量、章节分布、难度比例这三件事,直接决定抽出来的卷子质量。我不会在代码里写死,而是用一份字典作为组卷计划,换一场考试只改配置。

import random def build_paper(qbank: list, plan: dict, rng: random.Random) -> list: """ plan 示例: { "函数与高级特性": {"difficulty": {2: 3, 3: 2}}, "内置数据结构": {"difficulty": {1: 2, 2: 3}}, } 含义: 函数章节抽 diff=2 三道、diff=3 两道。 """ paper = [] for chapter, rule in plan.items(): pool = [q for q in qbank if q.get("chapter") == chapter] for diff, count in rule["difficulty"].items(): candidates = [q for q in pool if q.get("difficulty") == diff] if len(candidates) < count: raise ValueError(f"{chapter} diff={diff} 的题量不足") paper.extend(rng.sample(candidates, count)) return paper

rng.sample是不放回抽样,同一道题不会出现在同一份卷子里。rng必须外部传入,用random.Random(20240517)固定种子后,同一题库生成的两份卷子完全一致,方便按题号对答案或用 git 做试卷版本管理。

难度配比我一般参考这个比例:

难度分数占比建议适用场景
1-240%基础语法、内置数据结构
340%函数特性、面向对象、异常
4-520%经典算法、综合设计

4.2 选项乱序:答案字母必须跟着选项走

这是组卷脚本里最容易翻车的环节。只打乱options顺序而不改answer,原来答案是 B,打乱后正确选项变成了 C,判分就错了。正确做法是把正确文本找出来,打乱后再重新定位字母。

def shuffle_choices(q: dict, rng: random.Random) -> dict: option_letters = [chr(ord("A") + i) for i in range(len(q["options"]))] letter_to_text = dict(zip(option_letters, q["options"])) correct_text = letter_to_text[q["answer"]] new_options = list(q["options"]) rng.shuffle(new_options) new_answer = chr(ord("A") + new_options.index(correct_text)) return { **q, "options": new_options, "answer": new_answer, }

ord("A")把字母转成 ASCII 码,chr(65 + i)再把索引转回字母,不管题目有几个选项都可以通用。list.index定位正确项的新位置,遇到重复选项时会定位到第一个,所以清洗阶段就要保证同一道题里没有重复选项。

注意:多选题的答案用字符串接起来就能存,比如"ACD"。打乱选项后必须用同样的映射逻辑重标,不能只打乱题目不重标答案。

4.3 输出 Markdown 试卷和参考答案页

组好的卷子我一般同时输出两份:一份不含答案发给考生,一份带解析用于自测。Markdown 的好处是最低成本得到可打印格式,放进 Git 仓库也能直接渲染。

from pathlib import Path def dump_paper_md(paper: list, exam_path: str, answer_path: str) -> None: exam_lines, answer_lines = [], [] for idx, q in enumerate(paper, start=1): stem_md = f"{idx}. {q['stem']}" options_md = "\n".join( f" {chr(ord('A') + j)}. {opt}" for j, opt in enumerate(q["options"]) ) exam_lines.append(stem_md + "\n" + options_md) answer_lines.append( f"{idx}. 答案:{q['answer']}\n\n" f"解析:{q['analysis']}" ) Path(exam_path).write_text("\n\n".join(exam_lines), encoding="utf-8") Path(answer_path).write_text("\n\n".join(answer_lines), encoding="utf-8")

卷子和答案页共用同一个paper列表遍历,题号天然对齐,不会出现答案串位。解析为空时answer_lines里只保留答案行,不会把None写进文件。

4.4 多选题判分用集合比较

单选题用字符串相等没毛病,多选题不行——考生答案是「AC」,题库答案是「CA」,字符顺序不同但语义一样。判分时统一转成集合。

def grade(question: dict, user_answer: str) -> bool: expected = set(question["answer"]) actual = set(user_answer.upper()) return expected == actual

user_answer.upper()把用户输入的小写「ac」转成「AC」,再和题库答案比较。集合相等忽略顺序,「CA」和「AC」结果一致。这道函数也可以复用到第5章的刷题器里。

5. 一条命令开刷:把题库做成终端里的自测工具

5.1 最小可用的刷题循环

题库已经是 JSON,剩下的就是把刷题器做成一个本地命令行工具。输入答案即判分,一轮结束把错题落盘,下一轮优先刷错题。

import argparse import json import random def main() -> None: parser = argparse.ArgumentParser(description="Python 题库自测") parser.add_argument("--file", default="题库.json") parser.add_argument("--topic", default=None, help="只刷某个章节") parser.add_argument("--num", type=int, default=10, help="每轮题目数") args = parser.parse_args() qbank = json.load(open(args.file, encoding="utf-8")) if args.topic: qbank = [q for q in qbank if q["chapter"] == args.topic] picked = random.sample(qbank, min(args.num, len(qbank))) wrong = [] for q in picked: print(q["stem"]) for idx, opt in enumerate(q["options"]): print(f" {chr(65 + idx)}. {opt}") user = input("你的答案:").strip().upper() if set(user) == set(q["answer"]): print("对") else: wrong.append(q) print(f"错,正确答案:{q['answer']}") if q.get("analysis"): print(q["analysis"]) print(f"\n本轮共 {len(picked)} 题,错 {len(wrong)} 题") json.dump(wrong, open("错题.json", "w", encoding="utf-8"), ensure_ascii=False, indent=2)

--topic参数只按 chapter 一层过滤,想按 tags 过滤就把标签匹配加进去;--num超过题库总量时不会炸,random.sample会自动截断。错题落盘到「错题.json」,这个文件就是下一轮复习的数据源。

5.2 用错题文件做简单的间隔重复

间隔重复不需要重写算法,把「错题.json」里的题并进下一轮,让错误率高的题自然重复即可。

def merge_wrong(history: list, current_wrong: list, limit: int = 5) -> list: return (history + current_wrong)[-limit:]

[-limit:]只保留最近5道错题,避免错题列表无限膨胀。每次刷题前把wrong.json加载进来,先刷错题再刷新题,一个轻量的记忆曲线就成型了。题库里错误率最高的考点,就是这个脚本反复喂给你的内容。

5.3 换一台机器跑起来

这套工具不依赖 Visual Studio Code 或任何 IDE,只要机器上有 Python 3.9+ 解释器,在题库 JSON 同目录运行一行命令就能启动。如果还没装 Python,先按官方安装包装好,再到终端执行python --version验证。环境里缺 pdfplumber 时,运行pip install pdfplumber补上,其余部分都只用标准库。刷题器的启动命令是python quiz.py --topic 函数与高级特性 --num 5,把--topic换成考点表里的任意章节名,就能只刷某个薄弱点。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询