```
2. 不成组标签
```html
```
> 由于`input()`为Python内置方法,故创建``标签的方法为`input_tag()`。
这些标签均可通过`tag()`的方式创建,并可以通过`print(tag())`的方式打印,或通过`str(tag())`的方式转化为字符串。
除了内置标签外,PyGrading还支持创建自定义标签:
| custom_single_tag(tag_name) |
传入自定义的标签名称,创建一个不成组的标签 |
| custom(tag_name) |
传入自定义的标签名称,创建一个成组的标签 |
成组标签继承于`pygrading.html.Tag`类,包含有如下方法:
| Function |
Return |
Description |
| Tag.append(self, obj) |
None |
向当前标签实例中添加一个子标签 |
| Tag.pop(self, index=-1) |
None |
从当前标签实例中移除一个子标签,默认移除最后的一个 |
| Tag.insert(self, index, obj) |
None |
向当前标签实例中插入一个子标签 |
| Tag.extend(self, seq) |
None |
向当前标签实例中追加一个子标签列表 |
| Tag.set_text(self, src: str) |
None |
设定当前标签中的文本 |
| Tag.print(self) |
None |
将标签实例转化为HTML文本并打印到标准输出 |
| Tag.__str__(self) |
String |
重载方法,将标签实例转化为HTML文本字符串 |
| Tag.__lshift__(self, other) |
Tag |
重载操作符<<,用法同append,向当前标签实例中添加一个子标签 |
不成组标签继承于`pygrading.html.SingleTag`类,包含有如下方法:
| Function |
Return |
Description |
| SingleTag.print(self) |
None |
将标签实例转化为HTML文本并打印到标准输出 |
| SingleTag.__str__(self) |
String |
重载方法,将标签实例转化为HTML文本字符串 |
此外,该包还提供了一个方法用于将普通字符串中的换行符转换为HTML的换行符:
| Function |
Return |
Description |
| str2html(src: str) |
String |
将src字符串中的"\n"替换为" "并返回新的字符串 |
Tutorials
▴ Back to top
在本章中,将会通过例子,详细解析不同模块的用法。本章的所有例子均可在`example`目录下找到。
目录 (点击以展开...)
> - [构建并读取配置文件](#构建并读取配置文件)
> - [构建并读取评测用例](#构建并读取评测用例)
> - [评测结果的收集反馈](#评测结果的收集反馈)
> - [评测结果的打印输出](#评测结果的打印输出)
> - [评测结果的展示优化](#评测结果的展示优化)
### 构建并读取配置文件
在第一个例子中,我们将要演示如何构建并读取你的配置文件。由于在通用评测内核的实际使用中,教师账号没有权限操作实验环境的管理,因此对于不同的题目需要使用测试数据编辑器功能进行配置,配置文件就成为了用一个评测内核支持多个题目的关键。
配置文件是一个JSON格式的文本文件,用于向评测内核传递当前题目所需的配置项。一般来说,配置文件中的配置项至少应当包含:评测用例的数目(testcase_num)、评测用例的挂载路径(testcase_dir)和学生提交文件的挂载路径(submit_path)。如下面例子所示:
```json
{
"testcase_num": "5",
"testcase_dir": "./example/GettingStart/testdata",
"submit_path": "./example/GettingStart/submit/main.py"
}
```
当然,配置文件中的配置项可以根据实际情况自行添加,因为如何处理这些配置项也是由开发者决定的。
PyGrading提供了`load_config(source)`方法来读取配置文件,该方法传入配置文件的路径,返回一个由配置文件中的JSON串转换成的字典。我们推荐在评测任务预处理函数(prework)中完成配置文件的读取,并将配置信息传递给当前任务(job)的config属性,以便在其他函数中可以使用这些配置信息。
下面一段代码展示了如何读取并使用我们配置文件,由于还没配置评测用例,所以我们创建一个只包含prework和postwork的评测任务:
```python
import pygrading.general_test as gg
def prework(job: gg.Job):
config = gg.load_config("./example/构建并读取配置文件/config.json")
job.set_config(config)
def postwork(job: gg.Job):
config = job.get_config()
testcase_num = config["testcase_num"]
testcase_dir = config["testcase_dir"]
submit_path = config["submit_path"]
print("testcase_num:", testcase_num)
print("testcase_dir:", testcase_dir)
print("submit_path:", submit_path)
myjob = gg.job(prework=prework, run=None, postwork=postwork)
myjob.start()
```
执行结果如下:
```
testcase_num: 5
testcase_dir: ./example/GettingStart/testdata
submit_path: ./example/GettingStart/submit/main.py
```
以上就是构建并读取配置文件的方法。
### 构建并读取评测用例
接下来,我们尝试构建几组评测用例。评测用例一般来说是一组包含输入和输出的文件,每一组评测用例都会作为参数传递给评测用例执行函数(run())迭代执行并返回结果。
PyGrading推荐使用`gg.create_std_testcase()`方法进行评测用例实例的创建,该方法要求遵照标准形式配置评测用例目录,具体要求如下:
```
testdata
├── input
│ ├── input1.txt
│ ├── input2.txt
│ ├── input3.txt
│ ├── input4.txt
│ └── input5.txt
└── output
├── output1.txt
├── output2.txt
├── output3.txt
├── output4.txt
└── output5.txt
```
`testdata`为评测用例目录的根目录,建议将这个路径写入配置文件。根目录下分别设有`input`和`output`目录,分别用于存放输入和输出文件。
输入和输出文件的命名从`input1.txt`和`output1.txt`开始并一一对应。
在以此方式创建的评测用例实例中,总分默认为100分,传递给评测用例执行函数(run())的输入输出参数为每组`input`和`output`文件的路径,下面通过一段代码展示具体的使用方法:
```python
import pygrading.general_test as gg
def prework(job: gg.Job):
config = gg.load_config("./example/构建并读取评测用例/config.json")
testcases = gg.create_std_testcase(config["testcase_dir"], config["testcase_num"])
job.set_testcases(testcases)
def run(job: gg.Job, testcase: gg.TestCases.SingleTestCase):
print("######################")
print("Name:", testcase.name)
print("score:", testcase.score)
print("input_src:", testcase.input_src)
print("output_src:", testcase.output_src)
print("extension:", testcase.extension)
myjob = gg.job(prework=prework, run=run, postwork=None)
myjob.start()
```
执行结果如下:
```
######################
Name: TestCase1
score: 50.0
input_src: ./example/构建并读取评测用例/testdata_std/input/input1.txt
output_src: ./example/构建并读取评测用例/testdata_std/output/output1.txt
extension: None
######################
Name: TestCase2
score: 50.0
input_src: ./example/构建并读取评测用例/testdata_std/input/input2.txt
output_src: ./example/构建并读取评测用例/testdata_std/output/output2.txt
extension: None
```
上面的例子展示了如何使用推荐的方式构建、读取并使用评测用例,接下来我们将展示如何使用`gg.testcase()`方法来自定义评测用例。
假设根据要求,不必从文件中读取评测用例,而是直接由程序创建。参见下面代码实例:
```python
import pygrading.general_test as gg
def prework(job: gg.Job):
# 自定义评测用例总分
testcases = gg.create_testcase(100)
for i in range(1, 5):
input_src = i
output_src = pow(2, i)
# 使用append()方法向testcases追加评测用例
testcases.append("TestCase{}".format(i), 25, input_src, output_src)
job.set_testcases(testcases)
def run(job: gg.Job, testcase: gg.TestCases.SingleTestCase):
print("######################")
print("Name:", testcase.name)
print("score:", testcase.score)
print("input_src:", testcase.input_src)
print("output_src:", testcase.output_src)
print("extension:", testcase.extension)
myjob = gg.job(prework=prework, run=run, postwork=None)
myjob.start()
```
执行结果如下:
```
######################
Name: TestCase1
score: 25.0
input_src: 1
output_src: 2
extension: None
######################
Name: TestCase2
score: 25.0
input_src: 2
output_src: 4
extension: None
######################
Name: TestCase3
score: 25.0
input_src: 3
output_src: 8
extension: None
######################
Name: TestCase4
score: 25.0
input_src: 4
output_src: 16
extension: None
```
以上就是构建并读取评测用例的方法。
### 评测结果的收集反馈
我们在评测任务预处理阶段完成了读取配置文件和评测用例的任务,接下来评测用例将会传递给评测用例执行函数执行并收集结果。
如果在评测过程中需要执行Shell命令,可以使用`gg.utils.bash()`方法,该方法接收一个Shell命令字符串,返回值依次为:执行状态、执行过程输出、执行时间。
对于获取到的执行结果,我们提供了字符串比较和编辑距离比较两种方式:
1. 字符串比较支持单个字符串和字符串列表两种形式,返回结果为两个字符串是否相同的布尔值;
2. 编辑距离比较在接收到两个字符串列表时会返回两个列表之间的编辑距离,常用与按行比较的情况,返回结果为两个列表之间的编辑距离数值。
推荐以字典的形式收集并返回每个评测用例的执行结果,这些结果会保存在评测任务实例`job`的`summary`变量中,可以使用`job.get_summary()`来获取。
下面以一个简单的例子演示如何获取并收集评测结果:
```python
import pygrading.general_test as gg
def prework(job: gg.Job):
testcases = gg.create_testcase(100)
for i in range(1, 5):
input_src = i
output_src = pow(2, i)
testcases.append("TestCase{}".format(i), 25, input_src, output_src)
job.set_testcases(testcases)
def run(job: gg.Job, testcase: gg.TestCases.SingleTestCase):
# 使用Shell命令计算2^n
cmd = ["echo", "$((", "2", "**", str(testcase.input_src), "))"]
# 获取执行情况
status, output, time = gg.utils.bash(" ".join(cmd))
# 初始化返回结果的字典
result = {"name": testcase.name, "time": time}
# 获取评测用例给出的答案
answer = testcase.output_src
result["output"] = output
result["answer"] = answer
# 根据字符串比较结果返回单个测试用例的评判情况
if gg.utils.compare_str(str(output), str(answer)):
result["verdict"] = "Accept"
result["score"] = testcase.score
else:
result["verdict"] = "Wrong Answer"
result["score"] = 0
return result
def postwork(job: gg.Job):
# 打印收集到的每个评测用例的结果
print(job.get_summary())
myjob = gg.job(prework=prework, run=run, postwork=postwork)
myjob.start()
```
根据上面的例子,结合特定的评测用例情况,应当可以编写出满足需求的评测用例执行函数。
> 在实际应用中,应当在各个阶段的函数中对可能发生的异常进行捕获,以保证评测程序顺利执行,让学生能查看到正确的反馈信息并预防可能的作弊行为。
### 评测结果的打印输出
在评测任务执行完毕后,我们在评测结果处理函数中对收集到的评测结果进行最后处理并生成CG平台支持的JSON串格式,关于所支持的格式详情请查看[通用评测题开发指南](http://com.educg.net:8888/admin/help/projJudge.jsp#dockerImage)。
在评测任务实例`Job`中,有如下几个内置的方法,用于设定待输出的JSON串字段:
| Function |
Description |
| job.verdict() |
基本判定,一般为简要的评测结果描述或者简写,例如OJ系统的AC、PE、CE等 |
| job.rank() |
选择排行榜模式时,必须有该项,浮点数(正常值 ≥0),该值决定了本次提交在排行榜上的位置,排行榜从小到大排序。如果提交的材料有误或者其它异常,将rank值置为负数,不参与排行! |
| job.score() |
选择直接评测得分时,必须有该项,按照百分制给分,必须为大于等于0的整数,例如90 |
| job.images() |
可选,如果评测结果有图表,需要转换为base64或者SVG(启用HTML)格式 |
| job.comment() |
可选,评测结果的简要描述。 |
| job.detail() |
可选,评测结果的详细描述,可以包含协助查错的信息。布置作业的时候,可以选择是否显示这项信息。 |
| job.secret() |
可选,该信息只有教师评阅时才能看到。 |
| job.HTML() |
可选,如果置为enable,开发者可以使用HTML标签对verdict、comment、detail的输出内容进行渲染。 |
| job.custom() |
可选,自定义字段。 |
设定好JSON字段之后可以使用`job.print()`打印JSON字段到标准输出。
下面通过一个简单的示例展示如何配置评测任务的输出结果:
```python
import pygrading.general_test as gg
def postwork(job: gg.Job):
job.verdict("Accept")
job.score(100)
job.detail("Detail Message!")
job.custom("custom_key", "custom_value")
myjob = gg.job(prework=None, run=None, postwork=postwork)
myjob.start()
myjob.print()
```
输出结果如下:
```
{"verdict": "Accept", "score": "100", "rank": {"rank": "-1"}, "HTML": "enable", "detail": "Detail Message!", "custom_key": "custom_value"}
```
实际应用中,请使用`job.get_summary()`获取评测结果列表,再根据评测结果决定要输出的内容。
### 评测结果的展示优化
为了更好地展示评测结果,CG平台支持在返回JSON结果中的`verdict`、`comment`、`detail`、`secret`字段中添加HTML标签,经过渲染后显示为最终展示结果。
PyGrading中提供了强大的HTML文档构建工具`pygrading.html`,详细的API请参考[pygrading.html API](#pygradinghtml),接下来通过几个实例讲解各种场景的使用方法。
#### 1. 修改文本颜色
在`verdict`字段中我们通常希望展示如`Accept`、`Wrong Answer`这样的内容,为了让这些信息显示的更加直观,则需要给他们添加颜色。
下面创建一段HTML文本,对`Accept`显示为绿色,对`Wrong Answer`显示为红色:
```python
from pygrading.html import *
# 在括号中可以输入任意组键值对,他们将作为标签的属性显示在最终的HTML文本中
accept = font(color="green").set_text("Accept")
wrong_answer = font(color="red").set_text("Wrong Answer")
accept.print()
wrong_answer.print()
```
输出结果如下:
```html
Accept
Wrong Answer
```
显示效果如下:

#### 2. 创建表格
在`comment`、`detail`、`secret`字段中,通常需要表格进行内容的展示,接下来通过一个实例说明如何创建表格。
假设我们需要创建一个表格来比较每个评测用例中,学生输出的内容和标准答案的区别,解决方案如下:
```python
from pygrading.html import *
# 可以看到字符串中含有换行符,推荐使用str2html()函数进行处理,将换行符转化为
outputs = ["1", "1\n2", "1\n2\n3", "1\n2\n3\n4", "5\n4\n3\n2\n1"]
answers = ["1", "1\n2", "1\n2\n3", "1\n2\n3\n4", "1\n2\n3\n4\n5"]
# 标签之间可以相互嵌套,任意数量的子标签可以作为参数传递给父标签
result = table(
tr(
th().set_text("Output"),
th().set_text("Answer")
)
)
for out, ans in zip(outputs, answers):
tmp = tr(
td().set_text(str2html(out)),
td().set_text(str2html(ans)),
)
# 可以使用“<<”操作符将一个标签作为子标签传递给另一个标签
result << tmp
result.print()
```
生成HTML文本如下:
```html
| Output | Answer |
|---|
1
| 1
| 1 2
| 1 2
| 1 2 3
| 1 2 3
| 1 2 3 4
| 1 2 3 4
| 5 4 3 2 1
| 1 2 3 4 5
|
```
显示效果如下:
| Output | Answer |
|---|
1
| 1
| 1 2
| 1 2
| 1 2 3
| 1 2 3
| 1 2 3 4
| 1 2 3 4
| 5 4 3 2 1
| 1 2 3 4 5
|
#### 3. 创建表单
下面以创建一个用户名输入表单为例,展示如何创建不成对的HTML标签文本:
```python
from pygrading.html import *
# 由于input()为Python内置方法,故创建标签的方法为`input_tag()`
result = form(
font().set_text("First name"),
br(),
input_tag(type="text", name="firstname"),
br(),
font().set_text("Last name"),
br(),
input_tag(type="text", name="lastname"),
br(),
input_tag(type="submit", value="Submit")
)
result.print()
```
生成HTML文本如下:
```html
```
显示效果如下:

FAQ
▴ Back to top
**Q: 评测流程没有正确执行,但是为何程序执行结束没有任何报错信息?**
**A:** 在整个评测流程中,PyGrading会自动抓取执行过程中的异常,且保证这些异常不会影响评测程序的完整执行。
因此有些问题不会显示地报错,而是保存在`job.__result`和`job.__summary`对象中。如果发现执行问题,
可以在评测任务的最后,使用`print(your_job_name.get_result())`和`print(your_job_name.get_summary())`
查看评测过程中的日志。
**Q: 我在prework函数里面创建了一些变量希望能在run函数中使用,应该如何操作?**
**A:** 对于此类情况,可以通过向`gg.load_config()`返回的字典中添加一些您需要的信息组成键值对,再通过`gg.set_connfig()`函数将添加了新的键值对的函数赋值给当前job,即可再当前job中的所有函数中使用这些信息。 |