将错题转成独立练习

PythonBeginner
立即练习

介绍

保存稳定答案,并把仅属于本轮的错题转成独立练习。你需要前一单元的问答概念,以及 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 组合,保留原回合,并制作了独立错题练习,也正确处理了合法的空回合和提前退出。