Python自动评分器¶
此文件记录了 prairielearn/grader-python Docker 映像中包含的默认 Python 自动分级器。有关如何设置外部评分器的一般信息,请查看 外部评分 文档。
设置¶
info.json¶
问题应该首先设置启用外部评分,并在info.json设置中设置"gradingMethod": "External"。要使用本文档中详细介绍的特定 Python 自动评分器,应将 "externalGradingOptions" 字典中的 "image" 设置为 "prairielearn/grader-python"。不需要提供 entrypoint 属性。
完整的 info.json 文件应类似于:
{
"uuid": "...",
"title": "...",
"topic": "...",
"tags": ["..."],
"type": "v3",
"singleVariant": true,
"gradingMethod": "External",
"externalGradingOptions": {
"image": "prairielearn/grader-python"
}
}
用户变量¶
该问题必须指定将从学生代码中读取的变量用于对 (names_from_user) 进行评分。或者,问题还可以指定可从问题的设置代码 (names_for_user) 中供学生代码使用的变量。用户只能通过设置代码访问 names_for_user 中列出的变量或函数;用户代码中的测试用例只能访问 names_from_user 中列出的名称。
每个变量必须具有以下内容:
name:学生代码中变量的名称。description:人类可读的变量描述。type:人类可读的变量类型描述。这仅用于显示目的,不影响分级。
有两种方法可以指定这些变量。
===“question.html”
The `<pl-external-grader-variables>` element can be used to both specify and display a table of variables.
```html title="question.html"
<pl-external-grader-variables params-name="names_for_user">
<pl-variable name="n" type="integer">
Dimensionality of $\mathbf{A}$ and $\mathbf{b}$.
</pl-variable>
<pl-variable name="A" type="numpy array (shape $n \times n$)"> Matrix $\mathbf{A}$. </pl-variable>
<pl-variable name="b" type="numpy array (length $n$)"> Vector $\mathbf{b}$. </pl-variable>
</pl-external-grader-variables>
<pl-external-grader-variables params-name="names_from_user">
<pl-variable name="x" type="numpy array (length $n$)">
Solution to $\mathbf{Ax}=\mathbf{b}$.
</pl-variable>
</pl-external-grader-variables>
```
===“server.py”
Using `<pl-external-grader-variables>` in `question.html` is the recommended way to specify user variables. However, if you need to dynamically generate the lists of variables or have more advanced needs, you can also do so directly in `server.py` by assigning to `data["params"]["names_for_user"]` and `data["params"]["names_from_user"]`. The following example is equivalent to the previous `question.html` example:
```python title="server.py"
def generate(data):
data["params"]["names_for_user"] = [
{"name": "n", "description": r"Dimensionality of $\mathbf{A}$ and $\mathbf{b}$.", "type": "integer"},
{"name": "A", "description": r"Matrix $\mathbf{A}$.", "type": "numpy array"},
{"name": "b", "description": r"Vector $\mathbf{b}$.", "type": "numpy array"}
]
data["params"]["names_from_user"] = [
{"name": "x", "description": r"Solution to $\mathbf{Ax}=\mathbf{b}$.", "type": "numpy array"}
]
```
You can still use `<pl-external-grader-variables>` to display the variables to students; just omit the nested `<pl-variable>` elements.
```html title="question.html"
<pl-external-grader-variables params-name="names_for_user"></pl-external-grader-variables>
<pl-external-grader-variables params-name="names_from_user"></pl-external-grader-variables>
```
question.html¶
问题应包含学生提交文件的方式,例如 <pl-file-editor>、<pl-file-upload> 或工作区。问题还应包含 <pl-external-grader-results> 元素以显示评分结果。这些分别放置在问题面板和提交面板中。还建议在提交面板中放置 <pl-file-preview> 元素,以便学生可以看到他们以前提交的代码。下面给出了一个示例问题标记:
<pl-question-panel>
<pl-file-editor file-name="user_code.py" ace-mode="ace/mode/python"></pl-file-editor>
</pl-question-panel>
<pl-submission-panel>
<pl-external-grader-results></pl-external-grader-results>
<pl-file-preview></pl-file-preview>
</pl-submission-panel>
默认情况下,评分器将查找名为 user_code.py 的可评分文件,但这可以在测试套件中更改。
还可以使用 <pl-external-grader-variables> 元素向用户显示提供的和/或预期的变量。通过将 params-name 属性设置为 names_for_user 或 names_from_user,可以显示任意一组变量。
tests/setup_code.py¶
该文件在运行任何参考或学生代码之前执行。 names_for_user 中定义的任何变量都可以在学生代码中从此处访问,而参考答案可以不受限制地自由访问变量。对于学生代码和参考代码,此文件中的代码总共仅运行一次。如果您需要在每个学生和参考代码之前运行一些代码(例如,设置随机种子),您可以定义函数 def repeated_setup(),该函数将在每个学生和参考代码之前执行。 repeated_setup() 将始终在安装代码本身运行后运行。
data中的服务器参数可以通过该文件中的data来访问。
tests/test.py¶
每个编码问题的测试用例被定义为 test.py 文件中包含的 Test 类的方法。该课程将从 PLTestCase 或 PLTestCaseWithPlot 开始,具体取决于您是否需要学生提供绘图:
from pl_unit_test import PLTestCase, PLTestCaseWithPlot
# No plot grading
class Test(PLTestCase):
pass
# Plot grading enabled
class Test(PLTestCaseWithPlot):
pass
这些类本身扩展了 unittest.TestCase,因此其中的任何功能也可用。
每个测试用例都是类中的一个单独的方法,这些函数的名称必须以 test_ 为前缀,但后面的任何名称都是任意的。测试用例由 unittest 库按排序顺序运行,因此可以使用的一种约定是为测试提供数字名称以确保其运行顺序。
向测试用例添加名称和点值是通过 python 装饰器完成的:@points(val) 和 @name("name of the test case")。这些将控制向学生显示的案例名称和授予的分数。定义的一个例子:
from pl_unit_test import PLTestCase
from pl_helpers import name, points
class Test(PLTestCase):
@points(10)
@name("Check basic math")
def test_0(self):
assert 1 == 1
在测试用例实现中,学生答案变量和参考答案变量可以分别作为元组 self.st 和 self.ref 的子项进行访问。有各种辅助函数来检查不同类型变量的正确性,这些函数在 code_feedback.py 中定义。这些内容取自 RELATE 评分器,因此对于那些具有 RELATE 经验的人来说可能会很熟悉。
在测试用例结束时,使用 feedback.set_score() 设置答案的正确性。该函数采用 0 到 1 (inclusive) 之间的浮点数,0 表示完全不正确,1 表示完全正确。默认情况下,如果没有给出分数,测试用例将被标记为不正确。
测试用例的整体结构应该类似于:
from pl_unit_test import PLTestCase
from pl_helpers import name, points
from code_feedback import Feedback
class Test(PLTestCase):
@points(10)
@name("name of the test case")
def test_0(self):
if Feedback.check_scalar("name of the variable", self.ref.variable_name, self.st.variable_names):
Feedback.set_score(1)
else:
Feedback.set_score(0)
请注意,Feedback.set_score() 用于将测试用例的正确性设置在 0 和 1 之间,然后将其乘以测试用例授予的点数。例如,如果测试用例价值 10 分,并且运行 Feedback.set_score(0.5),则学生将获得 5 分。
可以使用 self.data 从测试用例内访问 data 中的服务器参数。
请参阅代码反馈文档,获取可以帮助您检查不同类型变量的正确性的函数列表。
前导和尾随代码¶
如果可选文件 tests/leading_code.py 和/或 tests/trailing_code.py 存在,自动评分器将在评分前自动将内容添加到用户提交的内容中。如果使用其他输入方法,例如,这可能很有用。对于 Parson 的问题提供 python 导入或其他必须运行的代码。
使用__name__ == "__main__" { #using-name-main }¶
默认情况下,学生的代码不会在 __name__ 设置为 "__main__" 的情况下执行,而作为 Python 脚本执行的 Python 脚本通常会出现这种情况。这允许学生使用如下代码在本地环境中测试和调试他们的代码,而不影响自动评分器功能:
if __name__ == "__main__":
# Student's own debugging code
另一方面,如果问题要求学生编写检查 __name__ 值的代码,则可以通过添加包含以下内容的 tests/leading_code.py 文件 来实现:
__name__ = "__main__"
多次迭代¶
通过设置 total_iters 类变量,测试套件可以运行多次迭代。为了防止特定的测试用例多次运行,您可以向其添加 @not_repeated 装饰器。
代码反馈¶
代码反馈库包含用于检查各种数据类型的正确性的内置函数。以下是它们的非详尽列表,有关更完整的参考,请参阅自动生成的代码文档 或GitHub 上的源文件。请注意,所有函数都会对用户输入执行某种健全性检查,并且如果学生未定义输入变量,则不会失败。
check_numpy_array_features(name, ref, data)检查 numpy 数组是否具有与参考解决方案相同的形状和数据类型。是否_不_根据参考检查值。check_numpy_array_allclose(name, ref, data, rtol=1e-05, atol=1e-08)检查 numpy 数组是否具有与参考解决方案相同的形状和数据类型,还使用指定的rtol和atol检查值以查看它们是否接近。check_list(name, ref, data, entry_type=None)检查列表的长度是否与参考解的长度相同。如果entry_type不是None,则可以选择检查每个元素是否具有该类型。是否_不_根据参考检查值。check_dict(name, ref, data, target_keys=None)检查学生字典是否具有相对于参考字典的所有正确键值映射。check_tuple(name, ref, data)检查元组是否具有与参考解决方案相同的长度和值。check_scalar(name, ref, data, rtol=1e-5, atol=1e-8)使用指定的rtol和atol检查标量值是否接近参考解。call_user(f, *args, **kwargs)使用特定的args和kwargs调用用户定义的函数。check_dataframe(name, ref, data, subset_columns=None)检查 Pandas DataFrame 是否具有与参考解决方案相同的内容。可以选择通过向subset_columns提供列名称列表来检查列的子集。
大多数这些函数都有 accuracy_critical 和 report_failure 关键字参数。如果 accuracy_critical 设置为 True(默认为 False),则如果此检查失败,评分作业将停止(类似于断言)。如果 report_failure 为 true (default),则如果此检查失败,将向学生显示反馈。显示反馈时使用 name 参数。
一般提示和陷阱¶
请注意,Feedback.check_xx 函数的第一个参数是正在检查的变量的名称,如果学生错误地回答了此问题,这将显示在评分者反馈中。
请注意不要调换学生参数和参考参数的顺序。学生的答案需要接受更严格的类型检查,过去也曾出现过因格式不良的学生答案而导致评分器崩溃的情况。
禁止库函数¶
注意,由于Python是一种高度动态的语言,因此对Python有足够了解的同学可以绕过以下方法。为了更好地保证哪些函数被使用或不被使用,请考虑使用更高级的静态分析技术,这些技术超出了此自动评分器提供的范围。您还可以通过手动评分进行手工验证。
人们可以在设置代码中挂钩库函数,以禁止学生访问某些函数。此示例取自 demo/autograder/python/numpy 问题。
可以将库函数设置为Feedback.not_allowed:
numpy.linalg.inv = Feedback.not_allowed
numpy.linalg.pinv = Feedback.not_allowed
现在,每当学生尝试使用这些函数时,他们的代码都会引发异常。
概述¶
特定于课程的库¶
有些课程可能会使用多个问题中通用的库。对于此类问题,可以将这些库和类保存在课程的 serverFilesCourse 目录中。此目录中的任何文件都会自动添加到 Python PATH 中,因此可以根据需要将它们导入到上述任何文件中。但是,如果使用此选项,问题的 info.json 文件应指示这些文件应添加到评分容器中,如下所示:
{
"externalGradingOptions": {
"image": "prairielearn/grader-python",
"serverFilesCourse": ["course_lib.py"]
}
}
上述过程适用于特定于特定课程的小型实用程序库。对于较大的库,包括从 Python 包索引 安装的库(即通过 pip install 或同等版本),强烈建议您创建您自己的自定义评分器图像,因为这将提供更好的性能并改善学生体验。
serverFilesCourse 静态数据的使用示例¶
Python 自动评分器能够从 serverFilesCourse 检索信息。当存在可以在多个问题之间共享的文件和其他静态数据时,可以使用此功能。例如,假设有一个名为 chem.json 的数据文件在多个问题中使用。该文件可以保存在课程根目录中的 serverFilesCourse 目录中,例如 serverFilesCourse/compounds/chem.json。
要从自动评分器访问 serverFilesCourse,请在问题 info.json 中指定文件或其包含目录。例如,要将 compounds 目录复制到自动评分器,请使用:
{
"externalGradingOptions": {
"image": "prairielearn/grader-python",
"serverFilesCourse": ["compounds"]
}
}
然后,test.py 中的测试可以使用如下代码加载 chem.json 文件:
import json
with open("/grade/serverFilesCourse/compounds/chem.json", "r") as f:
list_of_compounds = json.load(f)
安全¶
在评分作业开始时,脚本 run.sh 以 root 权限运行。这将初始化目录并将评分文件复制到正确的位置,删除自身,并使用 uuidgen 生成评分结果的秘密文件名,该文件名通过命令行参数传递到评分代码中。然后,实际的评分脚本将以用户 ag 的身份运行 (pltest.py)。
评分 Python 脚本会将所有敏感文件加载到内存中(设置代码、答案代码、测试用例),并在运行任何学生代码之前将其从磁盘中删除。 argv 中的命令行参数也会被擦除。因此,用户无法访问特定于问题的代码,而只能访问公开的通用评分器逻辑。
评分后,结果将写入上面生成的秘密文件名中。如果该文件不存在或文件名不匹配,则评分作业将失败,学生将不会获得分数。这主要是为了防止评分器代码崩溃,但理论上也可以防止狡猾的学生编写自己的结果文件。
评分作业现在将返回到 run.sh 脚本中的 root,并将所有输出复制到正确的位置,正如外部评分框架所期望的那样。