使用 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 通常所做的那样。
尽管这两个代码可能看起来相同,但它们在重要方面有所不同。
使用 Matlab,主代码 运行s 在主线程中。因此,任何事情都在全局 space 中完成,并且可以在主代码完成后访问(例如 x 和 y 变量)。对于 Python,Worker class 中的所有内容 运行,GraphicalUserInterface 中的所有内容 运行。因此主代码 运行 仅在局部 space 中,全局 space 中不保存任何内容(例如 x 和 y 在全局 space 中不存在)。这对我的项目来说是不可接受的,我需要访问全局 space.
中的所有内容
边缘:使用 Python 创建 GUI 会使主线程在 GUI 循环中保持忙碌,因此在主线程/python 提示符下无法执行任何其他操作.创建另一个线程对于 运行 其他任何事情都是必要的。另一方面,Matlab 非常乐意打开 GUI,同时让您的主线程可用于其他指令。这怎么可能?我的意思是,我到处都读到,让主线程忙于 GUI 循环对于 GUI 来说是绝对必要的,几乎在所有编程语言和应用程序上都是如此。然而 Matlab 清楚地证明这不是真的,并且您可以 运行 一个 GUI,同时仍然保持您的主线程可用。
所以我的两个问题是:
有没有办法让 PyQt5 有一个 GUI,然后 运行 你的主代码也在主线程中,这样一切都在全局 space 和主要代码结束后可以访问结果吗?
在最坏的情况下,有没有一种方法可以从第二个线程 运行 主代码,但之后仍然可以在全局 space 中生成 results/variables ?目前,我能想到的唯一选择是创建 GUI,在收集信息后以编程方式关闭它,运行 主代码,然后在主代码完成后再次打开 GUI。但真的,我们不能做得更好吗?
我们如何解释 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()
我有一个项目涉及图形界面和主要代码。我在 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 通常所做的那样。
尽管这两个代码可能看起来相同,但它们在重要方面有所不同。
使用 Matlab,主代码 运行s 在主线程中。因此,任何事情都在全局 space 中完成,并且可以在主代码完成后访问(例如 x 和 y 变量)。对于 Python,Worker class 中的所有内容 运行,GraphicalUserInterface 中的所有内容 运行。因此主代码 运行 仅在局部 space 中,全局 space 中不保存任何内容(例如 x 和 y 在全局 space 中不存在)。这对我的项目来说是不可接受的,我需要访问全局 space.
中的所有内容边缘:使用 Python 创建 GUI 会使主线程在 GUI 循环中保持忙碌,因此在主线程/python 提示符下无法执行任何其他操作.创建另一个线程对于 运行 其他任何事情都是必要的。另一方面,Matlab 非常乐意打开 GUI,同时让您的主线程可用于其他指令。这怎么可能?我的意思是,我到处都读到,让主线程忙于 GUI 循环对于 GUI 来说是绝对必要的,几乎在所有编程语言和应用程序上都是如此。然而 Matlab 清楚地证明这不是真的,并且您可以 运行 一个 GUI,同时仍然保持您的主线程可用。
所以我的两个问题是:
有没有办法让 PyQt5 有一个 GUI,然后 运行 你的主代码也在主线程中,这样一切都在全局 space 和主要代码结束后可以访问结果吗? 在最坏的情况下,有没有一种方法可以从第二个线程 运行 主代码,但之后仍然可以在全局 space 中生成 results/variables ?目前,我能想到的唯一选择是创建 GUI,在收集信息后以编程方式关闭它,运行 主代码,然后在主代码完成后再次打开 GUI。但真的,我们不能做得更好吗?
我们如何解释 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()