一、任务
上次的《用Python自动批改C语言作业(一)》提到对于末尾有getch();语句的C程序,Python未能成功运行它并捕获它的运行结果。为此,小编又做了今天的实验。
二、环境
外甥打灯笼——照旧(舅)。
Win7 32位 + Python3.64 + GCC编译器
三、程序
由于上次提到的四个程序当中,不带getch();语句的我们已经搞定了,剩下两个带getch();语句的程序只是编码的不同,而编码的不同我们可以通过设置不同的编译参数来解决。所以,我们只需搞定ANSI编码的带getch();语句的C程序的自动编译和运行问题就行了。这里给出一个简单的C程序文件test.c,代码如下:
#include <stdio.h>
#include <conio.h>
int main(int argc, char *argv[])
{
printf("姓名:张三\n年龄:23\n");
getch();
return 0;
}
四、思路
经过查阅互联网上众多的帖子,得到的结论是Python在执行外部exe文件的时候,os.popen功能有限,只能捕获exe的输出,但不能将exe所需的键盘输入数据喂给它。而getch();语句正是需要一个从键盘输入的任何字符,所以就没有办法了。很多人推荐用subprocess.Popen来代替os.popen。查了关于subprocess.Popen的一些资料,将代码
fileobj = os.popen("%s a"% exefn)
content = fileobj.read()
fileobj.close()
print(content)
修改为
p = subprocess.Popen(exefn, stdin = subprocess.PIPE,
stdout=subprocess.PIPE)
datasource = "a\n"
result = p.communicate(input = datasource.encode("gbk"))
content = result[0].decode("gbk")
print(content)
其它不变。为方便查看,这里给出全部代码。

五、运行
程序运行的时候,首先在Python输出窗口中输出了一些文字,然后出现一个黑黑的命令行窗口。如下图所示:

此时按一下任何一个按键,命令行窗口消失,Python输出窗口中多了一些文字,全部结果如下:
D:\ftp\c\test.c
文件编码:GB2312
编译输出文件名构造:D:\ftp\c\a.exe
编译字符串:gcc "D:\ftp\c\test.c" -g -finput-charset=GB2312 -fexec-charset=GBK -o "D:\ftp\c\a.exe"
程序输出结果:
姓名:张三
年龄:23
我们发现,这次修改代码,虽然也阻塞了程序的自动运行,但至少能出现黑窗口等着按一下键,不至于让我们请出杀进程的法宝。更重要的是,杀进程出不来任何结果,这里按一下任意键就能得到C程序的输出结果。这个效果我们还是较为满意的。美中不足的是,黑屏还在,并没有自动结束。
六、改进
按照subprocess.Popen的资料,我们用p.stdin.write(datasource)就可以为C程序提供所需要的输入数据,这里的C程序不就是需要我们按任意键吗?那我给提供了两个字符,它居然还接收不到数据?将"a\n"改为"a"或者"\n"或者"/n",均有黑屏出现等着按一下键。难道getch();语句比较顽固,油盐不进?
为此,改进了一下代码:
datasource = "a\n".encode("gbk")
改为
datasource = "张三丰\n100".encode("gbk")
同时,增加一个ANSI编码的C程序test2.c,内容如下:
#include <stdio.h>
#include <conio.h>
int main(int argc, char *argv[])
{
char name[100];
int age;
gets(name);
scanf("%d", &age);
printf("姓名:%s\n年龄:%d\n", name, age);
getch();
return 0;
}
然后,开始运行Python程序。照例要按一下键关掉过黑屏窗口,运行结果如下:
D:\ftp\c\test.c
文件编码:GB2312
编译输出文件名构造:D:\ftp\c\a.exe
编译字符串:gcc "D:\ftp\c\test.c" -g -finput-charset=GB2312 -fexec-charset=GBK -o "D:\ftp\c\a.exe"
程序输出结果:
姓名:张三
年龄:23
D:\ftp\c\test2.c
文件编码:ISO-8859-9
编译输出文件名构造:D:\ftp\c\a.exe
编译字符串:gcc "D:\ftp\c\test2.c" -g -finput-charset=ISO-8859-9 -fexec-charset=GBK -o "D:\ftp\c\a.exe"
编译失败,没有生成文件:D:\ftp\c\a.exe
第二个程序test2.c居然编译失败。原因是其编码被错误地识别成了ISO-8859-9,查资料发现这是土耳其语的编码。判断编码的函数getthe_encoding_of_txt_file用的是chardet扩展包:
def getthe_encoding_of_txt_file(fn):
from chardet import detect
with open(fn, "rb+") as fp:
content = fp.read()
encoding = detect(content)['encoding']
return encoding
它也有判断出错的时候。这不由让人想起“联通”两个字用记事本保存成ANSI编码再打开的惊喜变脸效果。既然这儿出了问题,我们得考虑修改这个函数本身或者修改该函数的返回值。咱们中国人编写的C程序,要么是AMNSI/GB2312/GBK系列编码,要么是UTF-8编码。我们可以考虑如果不是UTF-8编码的C程序,都通通按GBK编码来编译。因此,保留函数getthe_encoding_of_txt_file
不变,等收到返回值之后再修改这个值,确保是"UTF-8"和"GBK"之一。为此增加如下代码:
if encoding.lower() != "utf-8":
encoding = "gbk"
再次运行Python代码,按两次键忽略黑屏,此时的输出结果如下:
D:\ftp\c\test.c
文件编码:GB2312
编译输出文件名构造:D:\ftp\c\a.exe
编译字符串:gcc "D:\ftp\c\test.c" -g -finput-charset=gbk -fexec-charset=GBK -o "D:\ftp\c\a.exe"
程序输出结果:
姓名:张三
年龄:23
D:\ftp\c\test2.c
文件编码:ISO-8859-9
编译输出文件名构造:D:\ftp\c\a.exe
编译字符串:gcc "D:\ftp\c\test2.c" -g -finput-charset=gbk -fexec-charset=GBK -o "D:\ftp\c\a.exe"
程序输出结果:
姓名:张三丰
年龄:100
此时,我们发现,test2.c需要的两个数据(姓名和年龄)从Python程序传入成功。
可见,我们用Python的subprocess.Popen运行外部exe文件并为其传递数据的做法是没有问题的。有问题的只是getch();而已,它油盐不进,我们能有什么办法?
突然想到,以前在慕课网参加C语言学习的时候,凡是提交的作业,不许有提示性的输出,末尾不许用getch();语句,好像明白点什么了。
七、讨论
惹不起,俺躲还不行吗?
因为我们是自动批改作业,就无需让用户看看屏幕上输出的什么东西再按一下键结束黑屏了,凡是学生提交的C程序里面有getch();语句的地方,我们都给他换成return 0;语句。这样就不会出现黑屏等着按键进行干预了吧。我们捕捉到输出,再根据程序的具体要求去进一步处理就好了。
这个美妙主意,以后有时间再去尝试吧,俺得赶紧去让程序自动改C语言作业去了。
或许以后能有人找到搞定getch();语句的办法。




