Python2升级到Python3后Popen调用C++程序无法接收输入如何解决
问题根因
- Python3的
subprocess.Popen返回的标准输入/输出/错误流默认是字节流,和Python2默认的字符串流逻辑完全不同 - 原代码写入输入后没有手动刷新缓冲区也没有关闭标准输入,C++程序始终没收到完整输入,一直阻塞在读取输入的步骤,后续
stdout.readlines()也会跟着永久阻塞 - 编译阶段的错误判断逻辑在Python3中完全失效:
communicate()返回的stderr是字节类型,直接判断字符串'error'是否存在永远返回False,会直接跳过所有测试用例评分逻辑
可直接落地的修改方案
所有测试用例统一按以下逻辑修改,不需要手动write、不需要强制杀进程,communicate会自动处理输入发送、流关闭、进程等待的所有逻辑:
- 提前拼接好当前测试用例的所有输入内容,编码为utf-8字节
- 调用
communicate(输入字节)方法拿到进程返回的stdout、stderr - 将stdout解码为字符串后按行拆分,再做结果判断
- 编译阶段的错误判断先把stderr解码为字符串再做匹配
修改后的核心代码示例
from subprocess import Popen, PIPE def tester_1(filename): # 检测恶意system调用逻辑保留 if 'system' in open(filename, encoding='utf-8').read(): return 2 # 编译阶段修改:错误输出先解码再判断 p = Popen(["g++", filename], stdout=PIPE, stderr=PIPE) _, compile_err = p.communicate() grade = 4 # 字节直接判断是否有编译错误即可 if b'error' not in compile_err: grade += 1 # 第一个测试用例修改示例 test1_input = f"{3}\n{5}\n{7}\n{18}\n".encode('utf-8') p = Popen(['./a.out'], stdout=PIPE, stdin=PIPE, stderr=PIPE) stdout, _ = p.communicate(test1_input) result = stdout.decode('utf-8').splitlines() if len(result) >= 2: if "6" in result[-1] and "odd" in result[-1]: grade += 1 if "18" in result[-2] and "even" in result[-2]: grade += 1 # 第二个测试用例 try: test2_input = f"{2}\n{6}\n{7}\n".encode('utf-8') p = Popen(['./a.out'], stdout=PIPE, stdin=PIPE, stderr=PIPE) stdout, _ = p.communicate(test2_input) result = stdout.decode('utf-8').splitlines() if len(result) >= 2: if "7" in result[-1] and "odd" in result[-1]: grade += 1 if "6" in result[-2] and "even" in result[-2]: grade += 1 except: pass # 第三个测试用例 try: test3_input = f"{6}\n{2}\n{2}\n{4}\n{3}\n{5}\n{3}\n".encode('utf-8') p = Popen(['./a.out'], stdout=PIPE, stdin=PIPE, stderr=PIPE) stdout, _ = p.communicate(test3_input) result = stdout.decode('utf-8').splitlines() if len(result) >= 2: if "2.66" in result[-2] and "even" in result[-2]: grade += 1 if "3.66" in result[-1] and "odd" in result[-1]: grade += 1 except: pass grade = min(grade, 10) return grade
额外优化建议
- 可以去掉
shell=True参数,直接调用二进制文件不会有安全问题,还能减少进程开销 - 加超时参数避免学生代码死循环卡住评测脚本:
p.communicate(test_input, timeout=5),超时会抛出异常可以直接判对应测试用例0分
内容的提问来源于stack exchange,提问作者sboda
相关产品推荐
相关产品推荐

