介绍
保存稳定答案,并把仅属于本轮的错题转成独立练习。你需要前一单元的问答概念,以及 Python 函数、列表、字典、条件和循环。本单元独立准备好前面的三条规则,不依赖上一单元的文件或进程。
使用 WebIDE Terminal → New Terminal(窄窗口中在 More 下)。只编辑 logic.py,保留 engine.py、quiz.py 和 bank.json。预计阅读与练习共 35 分钟,并非实测的初学者用时。
将已提交答案保存为 JSON
本步骤保存真实回合,避免把答案绑定到屏幕位置。预备引擎返回的列表只包含已提交的 question_id/option_id 组合。引擎负责反馈和计分,记录里没有自报的正误或屏幕序号。
从 WebIDE 终端开始:
cd /home/labex/project
json.dump 将 Python 列表和字典写成 JSON。with open(..., "w", encoding="utf-8") 打开用于写入的文本文件,并在之后关闭。UTF-8 让文本可移植,ensure_ascii=False 保持 Unicode 可读,indent=2 让文件便于阅读。stream.write("\n") 在末尾写入换行。外层 {"answers": answers} 标明这个列表。
下面完整的 here-document 保留已有的三条规则,并新增保存函数。> 替换 logic.py,带引号的 <<'PY' 将文本原样写到最后的 PY。下一步再加入练习函数。
cat > logic.py <<'PY'
import json
def choose_questions(questions, count, rng):
return rng.sample(questions, min(count, len(questions)))
def move_options(question, rng):
options = question["options"].copy()
rng.shuffle(options)
return options
def read_answer(options, ask, tell):
while True:
text = ask("Answer (number or q): ").strip()
if text.lower() == "q":
return None
for position, option in enumerate(options, start=1):
if text == str(position):
return option["option_id"]
tell("Enter one displayed number, or q to stop.")
def save_answers(path, answers):
with open(path, "w", encoding="utf-8") as stream:
json.dump({"answers": answers}, stream, ensure_ascii=False, indent=2)
stream.write("\n")
PY
--ordered 保持已知的题目与选项顺序,--output 指定一个新的记录文件。已有输出路径会在开始游戏前被拒绝,以保护旧记录。回答两题:sky 输入 1(正确),tide 也输入 1(错误)。
python3 quiz.py bank.json --ordered --count 2 --output round.json
Score: 1/2 answered
Saved round.json
cat 显示真实记录:
cat round.json
它包含 sky/sun 和 tide/sun。同一个选项 ID 在两道题中的含义不同。保留这个原回合。如果不小心重跑,已有输出文件的错误是预期行为:额外实验应使用新文件名,不要覆盖旧文件。若源文件名输错,用 ls 检查,再以正确路径重跑;Quiz error: ... 不表示已保存回合。
仅重练本轮错题
本步骤依据每道题真正的正确 ID 重新判断错题。字典将已答题 ID 映射到所选选项 ID。仅当题目已经作答,并且答案不同于该题自己的 correct_option_id 时才保留。答对题和未答题都不能进入练习。列表保留完整的题目对象。在 {answer["question_id"]: answer["option_id"] for answer in answers} 中,for 后面的部分逐条访问答案,前面的键值部分构建字典条目,这叫字典推导式。在 [question for question in questions if ...] 中,for 逐题访问,if 只保留匹配对象,这叫列表推导式。and 要求两个条件同时成立:存在答案,而且答错了。因此未答题不会被当成错题。>> 追加内容,不替换已有规则:
cat >> logic.py <<'PY'
def wrong_questions(questions, answers):
selected = {answer["question_id"]: answer["option_id"] for answer in answers}
return [question for question in questions
if question["question_id"] in selected
and selected[question["question_id"]] != question["correct_option_id"]]
PY
--practice 读取原回合,--output 写入另一个新记录。它重练且仅重练该回合的全部错题,不累积历史。运行:
python3 quiz.py bank.json --practice round.json --output retry.json
只会出现 tide 题。读取 Moon 当前显示的序号并选择它,位置可能变化。结果为 Score: 1/1 answered。查看两个独立记录:
cat round.json retry.json
原记录仍是 tide/sun,重练记录是 tide/moon,题库不变。现在创建一轮全对的答案。题数为正数,并由 min(count, len(questions)) 限制上限;100 会将三题各问一次。
python3 quiz.py bank.json --ordered --count 100 --output perfect.json
sky、tide、roots 依次输入 1、2、2,然后重练该回合:
python3 quiz.py bank.json --practice perfect.json --output empty.json
No mistakes to practice.
Score: 0/0 answered
Saved empty.json
空错题集是合法结果。最后开始新回合,在作答前输入 q:
python3 quiz.py bank.json --output stopped.json
分数为 0/0,stopped.json 中的 answers 列表为空,不得为你离开的题目保存一个错误答案。保留全部五个记录。若编辑造成语法错误,重新执行该步骤的完整代码块;由于步骤 1 会替换文件,之后还需重新执行步骤 2 的追加块。保留已有记录,额外游玩时使用新文件名。
总结
你保存了真实提交的 ID 组合,保留原回合,并制作了独立错题练习,也正确处理了合法的空回合和提前退出。



