使用 Matlab 的主线程中的 GUI 和主代码 运行,没有等效的 PyQt5?

GUI and main code running in main thread with Matlab, no PyQt5 equivalent?

我有一个项目涉及图形界面和主要代码。我在 Matlab 和 Python 上开发项目。在项目中,图形界面从用户那里收集信息,然后在主代码中使用这些信息。理想情况下,界面先弹出,等待用户提供信息,然后主要代码是 运行 与信息,同时界面仍然打开但保持空闲。在 Matlab 中,这很简单,如以下最小示例所示(我想做的事情的简化版本)。

第一个文件:GraphicalUserInterface.m

classdef GraphicalUserInterface < handle

    properties (GetAccess = public, SetAccess= public)
        interface
        pushbutton
        wait
    end

    methods (Access = public)
    
        function self = GrapicalUserInterface()
            self.wait = true;
        end
    
        function launch_interface(self)
            % main window
            self.interface = figure();
            set(self.interface, 'units', 'pixels', 'position', [100 100 200 200]);
            % pushbutton
            self.pushbutton = uicontrol('style', 'pushbutton');
            set(self.pushbutton, 'unit', 'pixels', 'position', [50 50 100 100]);
            set(self.pushbutton, 'String', 'stop waiting');
            set(self.pushbutton, 'CallBack', @self.callback); 
        end
    
        function callback(self, hObject, callbackdata)
            self.wait = false;       
        end
    
    end        

end

第二个文件:main.m

clear
clc

% create gui
gui = GraphicalUserInterface();
gui.launch_interface;

% wait for user to be done with gui (gui.wait becomes 'false')
waitfor(gui, 'wait');

% run main code (here just some dummy code to verify everything works)
x = 2;
y = x + 3;
disp(y);

'GraphicalUserInterface'class定义界面,'main'脚本运行主要代码。 'main' 首先调用接口,然后用 'waitfor' 暂停执行。每当按下界面的单个按钮时,属性 'wait' 就会切换为 false 并且 waitfor 终止,从而允许主代码 运行。界面保持打开状态,一旦处理完主代码就可以再次使用。另外,在界面打开的情况下,我仍然可以使用提示符和运行其他脚本。

With Python,等价物将由以下代码给出,使用 PyQt5:

第一个文件:GraphicalUserInterface.m

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton
from PyQt5 import QtCore

class GraphicalUserInterface: 

    def __init__(self):
        self.wait = True
    
    def launch_interface(self):
        # main window
        self.app = QApplication(sys.argv)
        self.interface = QMainWindow()
        self.interface.setGeometry(100, 100, 200, 200)
        # pushbutton
        self.pushbutton = QPushButton(self.interface)
        self.pushbutton.move(50, 50)                                             
        self.pushbutton.resize(100, 100)
        self.pushbutton.setText('stop waiting')
        self.pushbutton.clicked.connect(self.callback)
        # display interface
        self.interface.show()
        sys.exit(self.app.exec_())
    
    def callback(self):    
        self.wait = False
        self.worker = Worker()
        self.worker.start()


class Worker(QtCore.QThread):

    def __init__(self, parent = None):
        super(Worker, self).__init__(parent)
        # run main code (here just some dummy code to verify everything works)
        x = 2
        y = x + 3
        print(y)

第二个文件:main.py

from GraphicalUserInterface import *

# create gui
gui = GraphicalUserInterface()
gui.launch_interface()

代码与Matlab略有不同。因为 PyQt5 运行s 仅在主线程上,所以我必须使用多线程和 运行 来自 Worker class 的主代码,就像 PyQt5 通常所做的那样。

尽管这两个代码可能看起来相同,但它们在重要方面有所不同。

  1. 使用 Matlab,主代码 运行s 在主线程中。因此,任何事情都在全局 space 中完成,并且可以在主代码完成后访问(例如 x 和 y 变量)。对于 Python,Worker class 中的所有内容 运行,GraphicalUserInterface 中的所有内容 运行。因此主代码 运行 仅在局部 space 中,全局 space 中不保存任何内容(例如 x 和 y 在全局 space 中不存在)。这对我的项目来说是不可接受的,我需要访问全局 space.

    中的所有内容
  2. 边缘:使用 Python 创建 GUI 会使主线程在 GUI 循环中保持忙碌,因此在主线程/python 提示符下无法执行任何其他操作.创建另一个线程对于 运行 其他任何事情都是必要的。另一方面,Matlab 非常乐意打开 GUI,同时让您的主线程可用于其他指令。这怎么可能?我的意思是,我到处都读到,让主线程忙于 GUI 循环对于 GUI 来说是绝对必要的,几乎在所有编程语言和应用程序上都是如此。然而 Matlab 清楚地证明这不是真的,并且您可以 运行 一个 GUI,同时仍然保持您的主线程可用。

所以我的两个问题是:

  1. 有没有办法让 PyQt5 有一个 GUI,然后 运行 你的主代码也在主线程中,这样一切都在全局 space 和主要代码结束后可以访问结果吗? 在最坏的情况下,有没有一种方法可以从第二个线程 运行 主代码,但之后仍然可以在全局 space 中生成 results/variables ?目前,我能想到的唯一选择是创建 GUI,在收集信息后以编程方式关闭它,运行 主代码,然后在主代码完成后再次打开 GUI。但真的,我们不能做得更好吗?

  2. 我们如何解释 Python 和 Matlab 之间的行为差​​异,后者显然更加灵活和宽松?

抱歉拖了这么久 post,非常感谢任何参与本次讨论的人。

所有 GUI 框架都使用专用的消息分发循环。 Matlab 只是向您隐藏了该循环。 Matlab 运行 比 Python 和 Qt 具有更多的抽象层,因此您不会注意到它。看起来很方便,但是这意味着你忽略了危险的做法。

Matlab 一直鼓励糟糕的编程实践。一切都是全局的,功能很笨拙,范围规则很松散。您可以提高工作效率,但任何大于几十行的内容都完全无法维护。

您对 Python 的全局 space 的评论是错误的。例如,如果您将 globals x, y 放在 Worker.__init__ 的开头,那么您将能够全局访问它们,但这是糟糕的编码习惯。在面向对象的语言中,您需要考虑您的对象。你有什么状态?哪些对象应该保持该状态?哪个状态需要共享?应该如何访问? Matlab 不会让您考虑这一点,这会导致错误的代码。

GUI 的规则是消息循环必须 运行 在创建 windows 的同一线程中。发起线程必须处理消息。在绝大多数情况下,该线程是主线程,一些框架确实做出了这样的假设,但这不是必需的。

但是,我鼓励你学会以正确的方式做事。在 Matlab 中工作的概念不会产生好的 Python 代码,即使它们工作。因此,将您的 GUI 代码放在主线程中,并通过响应事件来处理事情。通过将你的计算密集型代码变成一个线程。您将因此获得更好的应用程序。

如果您使用 asyncio 事件循环,您可以 运行 PyQt 主线程中的所有代码。例如:

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton
from PyQt5 import QtCore
import qasync
import asyncio

class GraphicalUserInterface:
    def __init__(self):
        self.wait = True

    def launch_interface(self):
        self.app = QApplication(sys.argv)
        self.interface = QMainWindow()
        self.interface.setGeometry(100, 100, 200, 200)
        self.pushbutton = QPushButton(self.interface)
        self.pushbutton.move(50, 50)
        self.pushbutton.resize(100, 100)
        self.pushbutton.setText('stop waiting')
        self.pushbutton.clicked.connect(self.callback)
        self.interface.show()

    def set_future(self, future):
        self.future = future

    def callback(self):
        self.future.set_result('button clicked')


class Worker():
    def __init__(self):
        x = 2
        y = x + 3
        print(y)

async def worker_loop(ui):
    i = 0
    while True:
        future = asyncio.get_event_loop().create_future()
        ui.set_future(future)
        result = await future
        Worker()
        i += 1
        if i == 4:
            break
    print("Done")

def main():
    ui = GraphicalUserInterface()
    ui.launch_interface()
    loop = qasync.QEventLoop(ui.app)
    asyncio.set_event_loop(loop)
    with loop:
        loop.run_until_complete(worker_loop(ui))

main()