PySide2中使用subprocess遇到句柄无效问题解决方案
前言
写了一个 Windows 端口转发工具的 GUI 版,将打包过程中遇到的问题记录下。
问题
使用 Pyinstaller 对程序进行打包,使用 -F
参数进行打包,运行没有任何问题,但是在运行时会先打开一个 cmd 进程(黑窗口),不美观,加上 -w
参数去掉黑窗口,完整的命令为:pyinstaller -F -w main.py
,打包后运行程序出现问题,如图:
去掉 -F
参数重新打包后,运行还是出现问题,也就是说无论是否打包成一个文件,只要有 -w
参数打包就会出问题。在这种情况下,一般的调试思路都是将 exe 文件拖到 cmd 中去运行,exe 执行结束后会打印出错误堆栈,帮助分析。诡异的是我的程序在 cmd 中运行后没有任何输出。
分析问题
这种情况下根据经验来猜测肯定是初始化时抛了异常没有捕获导致的,但是正常情况下不加 -w
参数是没有问题的,使用 IDE 运行也没有问题,不过为了确认问题还是捕获一下异常看看,捕获异常后运行结果如图:
不出所料,果然有问题,详细看了下问题出现在 subprocess.Popen
对象的 communicate()
方法上,代码如下:
1 | stdout_data, stderr_data = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
为什么会在这个方法上出现异常呢?
我去翻了翻 subprocess 的文档,发现这么一句话,如图:
子进程的标准输入、输入、错误都会在默认情况下继承自父进程,也就是说如果我们什么都不做的话标准输出和标准错误都会输出到我们当前进程的标准输出和标准错误。
1 | import subprocess |
1 | 112 |
可以看到,我们代码中只写了一个 print
函数,但是却输出了两行数据,第一行就是子进程中的 echo
程序写入到子进程标准输出中的信息,但是由于使用了默认参数,子进程继承了父进程的标准输出,所以子进程将信息写入到子进程的标准输出就等同于写入到了父进程的标准输出中。
如果我们参数 stdout
和 stderr
设置为 subprocess.PIPE
的话,就可以新建一个标准输出、标准错误的管道,从而将标准输出和标准错误的信息提取出来进行处理,而不是输出到父进程的标准输出和标准错误中。
1 | import subprocess |
1 | b'112\r\n' b'' |
可以看到,程序只输出了一行数据,是由 print
函数输出的,子进程的标准输出和标准错误都被重定向到了 out 变量和 err 变量中。标准输入同理,就不证明了。
看到这里,我们知道,子进程的标准输入、标准输出、标准错误既可以从父进程继承也可以新建一个新的管道,由主进程来控制。
继续看文档,我的程序中使用到了 communicate
函数,里面提到如果使用到了此函数,需要将 stdin
设置为 PIPE
(如下图),但是我的代码中只将 stdout
和 stderr
设置了 PIPE,忘记了将 stdin
设置为 PIPE
,默认为 None
的话会自动继承自父进程的标准输入,恰巧使用 -w
打包后父进程是一个 GUI 程序,可能没有标准输入?(这里存疑,如果有知道的小伙伴麻烦告知下,感谢🤝)没有标准输入的话就没法被子进程所继承,所以就导致了程序运行失败。
至于为什么使用 IDE 运行和不加 -w
参数时没有问题呢?这是因为在使用 IDE 运行和不加 -w
参数运行时,父进程是一个控制台程序,是有标准输入、标准输出、标准错误的,所以如果我在代码中不设置标准输入的话会自动继承父进程的标准输入,但是到了 GUI 程序中就不行了,不打开标准输入的话就会导致需要标准输入的 communicate
函数出现问题。
解决问题
问题原因清楚了,解决方案就很简单了,将标准输入设置为 PIPE
后,问题解决。
总结
出现问题还是要多看函数文档,好多内容要多读几遍文档才会被注意到,尤其是比较诡异的问题。
参考链接
https://docs.python.org/zh-cn/3/library/subprocess.html
本文章首发于个人博客 LLLibra146’s blog
本文作者:LLLibra146
更多文章请关注:
版权声明:本博客所有文章除特别声明外,均采用 © BY-NC-ND 许可协议。非商用转载请注明出处!严禁商业转载!
本文链接:
https://blog.d77.xyz/archives/d19da87a.html