如何在Python中实时分析执行其他脚本时的终端输入输出?
编程作业实时评分工具解决方案
我要开发一个编程作业评分工具,允许学生自主实现任务,不用限定具体写法。核心需求是实时分析终端的输入与输出,而不是等程序跑完再对比整个stdout。
之前试过subprocess.run、open、subprocess.Popen、exec()和pexpect,现有代码能对比最终输出,但存在漏洞——学生可以用硬编码输出蒙混过关。我不想一次性把整个文本文件作为输入传给程序,而是要逐行输入内容,实时捕获对应输出,验证每个输入的响应是否正确,还能定位复杂作业的特定检查点。
现有问题代码
import os import subprocess cwd = os.path.dirname(os.path.abspath(__file__)) expected_input = f"{cwd}/expected_input.txt" expected_path = f"{cwd}/expected_output.txt" def get_expected_output(file_path): return_str = "" with open(file_path, "r") as temp_file: for line in temp_file: if not line: break return_str += line return return_str def main(): expected_output = get_expected_output(expected_path) for x in range(1,4): test_path = f"{cwd}/test{x}.py" with subprocess.Popen(f"python {test_path} < {expected_input}", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, text=True ) as proc: std_out, std_err = proc.communicate() if std_out != expected_output: print(f"Expected: \n{expected_output} \n\nActual: \n{std_out}") print(f"Take a look at student {x}'s code") else: print(f"Student {x} finished") if __name__ == "__main__": main()
学生代码示例
正常通过的代码(Ex1)
import sys def main(): while(True): print("Please enter a number or type exit") user_input = input() if user_input == 'exit': sys.exit() try: num = float(user_input) print(f"{num} x 2 = {num*2}") except ValueError: print("Invalid input") if __name__ == "__main__": main()
(注:原代码存在逻辑bug,将input()转成float后与字符串'exit'比较永远不成立,此处已修正)
硬编码蒙混的代码(Ex2)
print("Please enter a number or type exit") print("1.0 x 2 = 2.0") print("Please enter a number or type exit") print("2.0 x 2 = 4.0") print("Please enter a number or type exit") print("3.0 x 2 = 6.0") print("Please enter a number or type exit")
解决方案:逐行交互验证
用pexpect可以实现逐行输入并实时捕获输出,精准验证每个输入对应的响应。以下是改进后的代码:
import os import pexpect cwd = os.path.dirname(os.path.abspath(__file__)) # 定义测试用例:输入内容与对应预期输出 test_cases = [ ("1.0", "1.0 x 2 = 2.0"), ("2.0", "2.0 x 2 = 4.0"), ("3.0", "3.0 x 2 = 6.0"), ("exit", pexpect.EOF) # 输入exit后进程应正常结束 ] def test_student_code(script_path): try: # 启动学生的Python脚本进程 child = pexpect.spawn(f"python {script_path}", cwd=cwd, encoding='utf-8') passed = True for input_str, expected_output in test_cases: # 等待程序输出提示信息 child.expect("Please enter a number or type exit") # 向进程输入测试内容 child.sendline(input_str) # 处理进程结束的情况 if expected_output == pexpect.EOF: try: child.expect(pexpect.EOF, timeout=1) except pexpect.TIMEOUT: print("错误:输入'exit'后进程未正常结束") passed = False continue # 验证程序的响应输出是否符合预期 child.expect(expected_output, timeout=1) print("测试通过" if passed else "测试未通过") except pexpect.TIMEOUT: print("超时:程序未按预期输出,疑似硬编码或逻辑错误") except pexpect.EOF: print("意外退出:程序提前结束,未完成所有测试用例") finally: if 'child' in locals(): child.close() def main(): for x in range(1,4): test_path = f"{cwd}/test{x}.py" print(f"===== 测试学生{x}的代码 =====") test_student_code(test_path) if __name__ == "__main__": main()
方案说明
- 用
pexpect.spawn启动交互式进程,替代一次性输入所有内容的方式,实现真实的终端交互逻辑 - 针对每个测试用例,先等待程序输出提示信息,再输入测试值,最后验证对应输出,完全模拟用户操作流程
- 能精准识别硬编码行为:硬编码代码只会按固定顺序输出内容,不会响应输入,第一次输入后就会超时,无法匹配预期输出
- 可灵活扩展测试用例,比如加入非数字输入等异常场景,验证程序的错误处理能力
内容的提问来源于stack exchange,提问作者whatever
相关产品推荐
相关产品推荐

