如何捕获长时间运行的程序的输出并在Python的GUI中显示它?

2024-09-29 21:54:17 发布

您现在位置:Python中文网/ 问答频道 /正文

This is what I need to embedded into my GUI

我会尽量弄清楚的。在

我有一个控制电源的非常简单的测试脚本,该脚本测量来自被测安捷伦电源+单元的一些电流,然后,脚本将这些读数打印如下:

PS.write(b"MEAS:CURR? \n")
time.sleep(2)
response = PS.read(1000)
time.sleep(3)
print(response)
(float(response)*1)
E3632A=(float(response)*1)
print (E3632A)

当脚本执行“打印命令”(print(E3632A)时,所有信息都显示在py.exe文件“DOS窗口(C:\Windows\py.exe文件). 这是我的问题

如何将其嵌入到一个简单的GUI中?我希望我的图形用户界面显示py.exe文件正在显示。很简单。。。我在网上看过所有的帖子,但没有一个真正的解决办法。在


Tags: 文件py脚本timeresponsesleepfloatexe
1条回答
网友
1楼 · 发布于 2024-09-29 21:54:17

假设您调用的进程是长时间运行的,并且不会一次性生成所有输出,这意味着您不能使用subprocess.Popen.communicate(),因为它的目的是读取文件末尾的所有输出。在

你必须使用其他标准技术来读取管道。在

由于您希望将其与GUI集成,并且该过程是长期运行的,因此您需要将读取其输出与GUI的主循环相协调。这使事情有些复杂。在

特金特

首先假设您想要使用TkInter,如您的一个例子所示。这给我们带来了几个问题:

  • TkInter与选择模块没有集成。在
  • 到目前为止,甚至还没有TkInter与asyncio的规范集成(另请参见https://bugs.python.org/issue27546)。在
  • 通常建议不要使用root.update()来破解一个定制的主循环,让我们用线程来解决本应是基于事件的方法。在
  • TkInter的event_generate()缺少Tk随事件一起发送用户数据的功能,因此我们不能使用TkInter事件将接收到的输出从一个线程传递到另一个线程。在

因此,我们将使用线程来处理它(即使我不想这样做),其中主线程控制TKGUI,辅助线程读取进程的输出,并且TkInter中缺少本地方法来传递数据,我们使用线程安全的队列。在

#!/usr/bin/env python3

from subprocess import Popen, PIPE, STDOUT, TimeoutExpired
from threading import Thread, Event
from queue import Queue, Empty
from tkinter import Tk, Text, END


class ProcessOutputReader(Thread):

    def __init__(self, queue, cmd, params=(),
                 group=None, name=None, daemon=True):
        super().__init__(group=group, name=name, daemon=daemon)
        self._stop_request = Event()
        self.queue = queue
        self.process = Popen((cmd,) + tuple(params),
                             stdout=PIPE,
                             stderr=STDOUT,
                             universal_newlines=True)

    def run(self):
        for line in self.process.stdout:
            if self._stop_request.is_set():
                # if stopping was requested, terminate the process and bail out
                self.process.terminate()
                break

            self.queue.put(line)  # enqueue the line for further processing

        try:
            # give process a chance to exit gracefully
            self.process.wait(timeout=3)
        except TimeoutExpired:
            # otherwise try to terminate it forcefully
            self.process.kill()

    def stop(self):
        # request the thread to exit gracefully during its next loop iteration
        self._stop_request.set()

        # empty the queue, so the thread will be woken up
        # if it is blocking on a full queue
        while True:
            try:
                self.queue.get(block=False)
            except Empty:
                break

            self.queue.task_done()  # acknowledge line has been processed


class MyConsole(Text):

    def __init__(self, parent, queue, update_interval=50, process_lines=500):
        super().__init__(parent)
        self.queue = queue
        self.update_interval = update_interval
        self.process_lines = process_lines

        self.after(self.update_interval, self.fetch_lines)

    def fetch_lines(self):
        something_inserted = False

        for _ in range(self.process_lines):
            try:
                line = self.queue.get(block=False)
            except Empty:
                break

            self.insert(END, line)
            self.queue.task_done()  # acknowledge line has been processed

            # ensure scrolling the view is at most done once per interval
            something_inserted = True

        if something_inserted:
            self.see(END)

        self.after(self.update_interval, self.fetch_lines)


# create the root widget
root = Tk()

# create a queue for sending the lines from the process output reader thread
# to the TkInter main thread
line_queue = Queue(maxsize=1000)

# create a process output reader
reader = ProcessOutputReader(line_queue, 'python3', params=['-u', 'test.py'])

# create a console
console = MyConsole(root, line_queue)

reader.start()   # start the process
console.pack()   # make the console visible
root.mainloop()  # run the TkInter main loop

reader.stop()
reader.join(timeout=5)  # give thread a chance to exit gracefully

if reader.is_alive():
    raise RuntimeError("process output reader failed to stop")

由于前面提到的警告,TkInter代码最终有点偏大。在

PyQt公司

相反,使用PyQt可以大大改善我们的情况,因为该框架已经提供了一种与子流程集成的本地方法,其形式是其QProcess类。在

这意味着我们可以去掉线程,而使用Qt的原生信号机制。在

^{pr2}$

我们最终得到了一个从Qt类派生的小样板,但是使用了一个整体更干净的方法。在

一般注意事项

还要确保您调用的进程没有缓冲多个输出行,否则它看起来仍然像是控制台卡住了。在

特别是如果被调用方是python程序,您可以确保它使用print(..., flush=True),或者用python -u callee.py调用它来强制执行无缓冲输出。在

相关问题 更多 >

    热门问题