如何使用子进程或 QProcess 启动独立的 gnuplot 进程?

How to start an independent gnuplot process with either subprocess or QProcess?

已经有很多关于 subprocessQProcess 的问题和答案,但我还没有找到我的问题的答案。所以,如果我在这一堆部分令人困惑或过时的答案中没有找到答案,请原谅。

这听起来像是一项简单的任务:在 PyQt GUI 应用程序中,我想启动一个独立的(子)进程。 在我的例子中:gnuplot,一个绘图工具,带有选项“-p”(持久性,即在绘图完成后保持 window 打开)。一旦启动,它应该继续 运行 作为一个独立的(交互式)应用程序。

的文档 QProcess and subprocess 没有进一步帮助我。

代码:(我的精简最小示例,没有按预期工作)

from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QPushButton, QVBoxLayout
from PyQt5.QtCore import pyqtSlot, QProcess
import sys, subprocess

class App(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()
        
    def initUI(self):
        self.setGeometry(0,0,700,500)
        self.layout = QVBoxLayout()

        self.pb_plot_1 = QPushButton("Plot via subprocess")
        self.pb_plot_1.clicked.connect(self.on_click_pb_plot_1)
        self.layout.addWidget(self.pb_plot_1)

        self.pb_plot_2 = QPushButton("Plot via QProcess")
        self.pb_plot_2.clicked.connect(self.on_click_pb_plot_2)
        self.layout.addWidget(self.pb_plot_2)
        
        self.pb_plot_3 = QPushButton("Plot via QProcess and loading a file")
        self.pb_plot_3.clicked.connect(self.on_click_pb_plot_3)
        self.layout.addWidget(self.pb_plot_3)
        
        self.setLayout(self.layout) 
        self.show()

    @pyqtSlot()
    
    def get_plot_code(self):
        return '''
        # some gnuplot code, in reality much longer
        set term wxt
        plot sin(x)
        '''
        
    def on_click_pb_plot_1(self):
        gnuplotPath = r'gnuplot\bin\gnuplot'
        gnuplotCode = self.get_plot_code()
        proc = subprocess.Popen([gnuplotPath, '-p'], stdin=subprocess.PIPE)
        proc.communicate(gnuplotCode.encode())

    def on_click_pb_plot_2(self):
        gnuplotPath = r'gnuplot\bin\gnuplot'
        gnuplotCode = self.get_plot_code()
        proc = QProcess(self)
        proc.start(gnuplotPath, ["-p"])
        proc.write(gnuplotCode.encode())
     
    def on_click_pb_plot_3(self):
        gnuplotPath = r'gnuplot\bin\gnuplot'
        gnuplotFile = 'gnuplotFile.gp'
        proc = QProcess(self)
        proc.start(gnuplotPath, ["-p", gnuplotFile])
        
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

结果:

如果我按下按钮,每次都会打开 gnuplot 并显示图表。到目前为止,好的。 然而,

通过子进程: gnuplot 将作为交互式工作 window,但我的 PyQt 应用程序将冻结(不响应)直到我关闭 gnuplot。 subprocess.Popen 似乎有选项 close_fds=True,它应该可以做我想做的,但在 Windows 中,这似乎不能与通过 stdin=subprocess.PIPE 发送命令一起工作。

Note that on Windows, you cannot set close_fds to true and also redirect the standard handles by setting stdin, stdout or stderr

那么,我还能用 subprocess 怎么做呢?添加 proc.terminate() 将无济于事。

通过 QProcess: gnuplot 将冻结并且不会作为交互式工作 window(无响应)。如果我杀死 gnuplot,我可以重新开始,但 gnuplot 再次会冻结。

我也尝试过 startDetached() 之类的...但没有成功...

我可以想象这个问题可以通过对代码进行微小的更改来解决。但我不知道如何改变。 所以,我确定这是我对文档的有限理解,而且我缺少合适的示例。 我的配置:Win10,Python3.6.3,PyQt 5.11.3

更新:(上面的代码已更改,添加了第三个变体)

通过 QProcess 并读取文件(而不是将代码写入 gnuplot)

a) gnuplot 将创建绘图,b) 不会冻结任何内容,c) gnuplot 保持交互,d) 我可以独立关闭我的应用程序或 gnuplot。 但是,我不想从磁盘读取 gnuplot 文件,但我想将代码发送到 gnuplot(我假设是 stdin)。

更新二:

也许这可能是对可以解释这一点的人的提示。这似乎取决于 gnuplot 版本。以下结果:

5.2.0 QWindowsPipeWriter::write failed. (The handle is invalid.)
5.2.2 QWindowsPipeWriter::write failed. (The handle is invalid.)
5.2.3 QWindowsPipeWriter::write failed. (The handle is invalid.)
5.2.4 QWindowsPipeWriter::write failed. (The handle is invalid.)
5.2.5 QWindowsPipeWriter::write failed. (The handle is invalid.)

5.2.6 works
5.2.7 works
5.2.8 works

5.4.0 freezing
5.4.1 freezing

好的,@eyllanesc 是对的...我将 PyQt5 从 5.11.3 更新到 5.15.4,问题似乎不再出现了。无论这是什么错误或不兼容......