問題描述
我想用 PyQt5 運行命令.我想按時間順序實時獲取標準輸出和標準錯誤.
I want to run command with PyQt5. And I want to get the stdout and stderr in time order and in real-time.
我分為 UI 類和 Worker 類.有幾個 UI 類,但為簡單起見,我只指定了一個.
I separated into UI class and Worker class. There are several UI classes, but for simplicity, I've specified just one.
我試圖解決這個問題,但我不能.我無法在 Worker 線程和 logger 函數之間建立連接.
I've tried to solve this problem, but I can't. I can't connect between the Worker thread and logger function.
test_ui.py
import sys
import subprocess
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout
from PyQt5.QtWidgets import QPushButton, QTextEdit
from worker import Worker
class TestUI(QWidget):
def __init__(self):
super().__init__()
self.worker = Worker()
self.btn1 = QPushButton("Button1")
self.btn2 = QPushButton("Button2")
self.btn3 = QPushButton("Button3")
self.result = QTextEdit()
self.init_ui()
def init_ui(self):
self.btn1.clicked.connect(self.press_btn1)
self.btn2.clicked.connect(self.press_btn2)
self.btn3.clicked.connect(self.press_btn3)
hlayout1 = QHBoxLayout()
hlayout1.addWidget(self.btn1)
hlayout1.addWidget(self.btn2)
hlayout1.addWidget(self.btn3)
hlayout2 = QHBoxLayout()
hlayout2.addWidget(self.result)
vlayout = QVBoxLayout()
vlayout.addLayout(hlayout1)
vlayout.addLayout(hlayout2)
self.setLayout(vlayout)
self.show()
def press_btn1(self):
command1 = "dir"
path = "./"
self.worker.run_command(command1, path)
self.worker.outSignal.connect(self.logging)
def press_btn2(self):
command2 = "cd"
path = "./"
self.worker.run_command(command2, path)
self.worker.outSignal.connect(self.logging)
def press_btn3(self):
command3 = "whoami"
path = "./"
self.worker.run_command(command3, path)
self.worker.outSignal.connect(self.logging)
def logging(self, str):
self.result.append(str.strip())
if __name__ == "__main__":
APP = QApplication(sys.argv)
ex = TestUI()
sys.exit(APP.exec_())
worker.py
from PyQt5.QtCore import QProcess, pyqtSignal
class Worker:
outSignal = pyqtSignal(str)
errSignal = pyqtSignal(str)
def __init__(self):
self.proc = QProcess()
def run_command(self, cmd, path):
self.proc.setWorkingDirectory(path)
self.proc.setProcessChannelMode(QProcess.MergedChannels)
self.proc.readyReadStandardOutput.connect(self.onReadyStandardOutput)
self.proc.finished.connect(self.proc.deleteLater)
self.proc.start(cmd)
def onReadyStandardOutput(self):
result = self.proc.readAllStandardOutput().data().decode()
self.outSignal.emit(result)
def onReadyStandardError(self):
result = self.proc.readAllStandardError().data().decode()
self.errSignal.emit(result)
更新:
應用 here 解決方案并進行以下修改仍然會使代碼失敗:
Applying here solution and making the following modifications still fails the code:
@pyqtSlot()
def press_btn1(self):
command1 = "dir"
path = "./"
self.worker.run_command(command1, path)
@pyqtSlot()
def press_btn2(self):
command2 = "cd"
path = "./"
self.worker.run_command(command2, path)
@pyqtSlot()
def press_btn3(self):
command3 = "test.bat"
path = "./"
self.worker.run_command(command3, path)
@pyqtSlot(str)
def logging(self, msg):
msg = msg.strip()
if msg != "":
self.result.append(msg)
test.bat
@echo off
echo "Output 1"
timeout /t 1
1>&2 echo "Error 1"
timeout /t 1
echo "Output 2"
timeout /t 1
1>&2 echo "Error 2"
<小時>
批處理文件問題
這是我通過命令提示符運行它時的結果.
Batchfile Issue
This is the result when I run it through the command prompt.
每秒實時輸出一行.
"Output 1"
Waiting for 0 seconds, press a key to continue ...
"Error 1"
Waiting for 0 seconds, press a key to continue ...
"Output 2"
Waiting for 0 seconds, press a key to continue ...
"Error 2"
這是應用程序的結果.
它在 3 秒后輸出整行.而且時間順序不對.
It outputs whole lines after 3 seconds. And the time order is not right.
"Output 1"
Waiting for 1 seconds, press a key to continue ...0
Waiting for 1 seconds, press a key to continue ...0
"Output 2"
Waiting for 1 seconds, press a key to continue ...0
"Error 1"
"Error 2"
推薦答案
你有以下錯誤:
信號只在QObjects中起作用,因此Worker需要繼承QObject.
The signals only work in the QObjects so it is necessary for Worker to inherit from QObject.
建議 QProcess 不是該類的成員,因為我們說任務 1 正在執行,而您沒有完成就嘗試執行任務 2,這樣任務 1 將被替換,這不是您想要的,相反,QProcess 可以作為 Worker 的子對象,這樣您的生命周期就不會局限于創建它的方法.
It is recommended that QProcess not be a member of the class since we say that task 1 is being executed and without finishing you try to execute task 2 so that task 1 will be replaced which is not what you want, instead QProcess can be done be a child of Worker so that your life cycle is not limited to the method where it was created.
如果你想分別監控 stderr 和 stdio 輸出,那么你不應該喜歡 processChannelMode 到 QProcess::MergedChannels 因為這將加入兩個輸出,另一方面,如果上述被消除,那么你必須使用 readyReadStandardError通知 stderr 何時被修改的信號.
If you want to monitor the stderr and stdio output separately then you should not like processChannelMode to QProcess::MergedChannels since this will join both outputs, on the other hand if the above is eliminated then you must use the readyReadStandardError signal to know when stderr is modified.
由于QProcess不是該類的成員,因此在onReadyStandardOutput和onReadyStandardError中獲取QProcess是很困難的,但是為此必須使用sender()方法,該方法具有發出信號的對象.
Since QProcess is not a member of the class, it is difficult to obtain the QProcess in onReadyStandardOutput and onReadyStandardError, but for this you must use the sender() method that has the object that emitted the signal.
信號和插槽之間的連接只能建立一次,在您的情況下,您在 press_btn1、press_btn2 和 press_btn3 中執行此操作,因此您將獲得 3 倍相同的信息.
The connections between signals and slot should only be made once, in your case you do it in press_btn1, press_btn2 and press_btn3 so you will get 3 times the same information.
不要使用 str
,因為它是一個內置函數.
Do not use str
since it is a built-in function.
綜合以上,解決辦法是:
Considering the above, the solution is:
worker.py
from PyQt5.QtCore import QObject, QProcess, pyqtSignal, pyqtSlot
class Worker(QObject):
outSignal = pyqtSignal(str)
errSignal = pyqtSignal(str)
def run_command(self, cmd, path):
proc = QProcess(self)
proc.setWorkingDirectory(path)
proc.readyReadStandardOutput.connect(self.onReadyStandardOutput)
proc.readyReadStandardError.connect(self.onReadyStandardError)
proc.finished.connect(proc.deleteLater)
proc.start(cmd)
@pyqtSlot()
def onReadyStandardOutput(self):
proc = self.sender()
result = proc.readAllStandardOutput().data().decode()
self.outSignal.emit(result)
@pyqtSlot()
def onReadyStandardError(self):
proc = self.sender()
result = proc.readAllStandardError().data().decode()
self.errSignal.emit(result)
test_ui.py
import sys
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QApplication, QGridLayout, QPushButton, QTextEdit, QWidget
from worker import Worker
class TestUI(QWidget):
def __init__(self):
super().__init__()
self.worker = Worker()
self.worker.outSignal.connect(self.logging)
self.btn1 = QPushButton("Button1")
self.btn2 = QPushButton("Button2")
self.btn3 = QPushButton("Button3")
self.result = QTextEdit()
self.init_ui()
def init_ui(self):
self.btn1.clicked.connect(self.press_btn1)
self.btn2.clicked.connect(self.press_btn2)
self.btn3.clicked.connect(self.press_btn3)
lay = QGridLayout(self)
lay.addWidget(self.btn1, 0, 0)
lay.addWidget(self.btn2, 0, 1)
lay.addWidget(self.btn3, 0, 2)
lay.addWidget(self.result, 1, 0, 1, 3)
@pyqtSlot()
def press_btn1(self):
command1 = "dir"
path = "./"
self.worker.run_command(command1, path)
@pyqtSlot()
def press_btn2(self):
command2 = "cd"
path = "./"
self.worker.run_command(command2, path)
@pyqtSlot()
def press_btn3(self):
command3 = "whoami"
path = "./"
self.worker.run_command(command3, path)
@pyqtSlot(str)
def logging(self, string):
self.result.append(string.strip())
if __name__ == "__main__":
APP = QApplication(sys.argv)
ex = TestUI()
ex.show()
sys.exit(APP.exec_())
更新:
QProcess 對執行控制臺命令(例如 .bat)有限制,因此在這種情況下,您可以通過在另一個線程中執行 subprocess.Popen 并通過信號發送信息來使用它:
QProcess has limitations to execute console commands such as .bat so in this case you can use subprocess.Popen by executing it in another thread and sending the information through signals:
worker.py
import subprocess
import threading
from PyQt5 import QtCore
class Worker(QtCore.QObject):
outSignal = QtCore.pyqtSignal(str)
def run_command(self, cmd, **kwargs):
threading.Thread(
target=self._execute_command, args=(cmd,), kwargs=kwargs, daemon=True
).start()
def _execute_command(self, cmd, **kwargs):
proc = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs
)
for line in proc.stdout:
self.outSignal.emit(line.decode())
test_ui.py
import sys
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QApplication, QGridLayout, QPushButton, QTextEdit, QWidget
from worker import Worker
class TestUI(QWidget):
def __init__(self):
super().__init__()
self.worker = Worker()
self.worker.outSignal.connect(self.logging)
self.btn1 = QPushButton("Button1")
self.btn2 = QPushButton("Button2")
self.btn3 = QPushButton("Button3")
self.result = QTextEdit()
self.init_ui()
def init_ui(self):
self.btn1.clicked.connect(self.press_btn1)
self.btn2.clicked.connect(self.press_btn2)
self.btn3.clicked.connect(self.press_btn3)
lay = QGridLayout(self)
lay.addWidget(self.btn1, 0, 0)
lay.addWidget(self.btn2, 0, 1)
lay.addWidget(self.btn3, 0, 2)
lay.addWidget(self.result, 1, 0, 1, 3)
@pyqtSlot()
def press_btn1(self):
command1 = "dir"
path = "./"
self.worker.run_command(command1, cwd=path)
@pyqtSlot()
def press_btn2(self):
command2 = "cd"
path = "./"
self.worker.run_command(command2, cwd=path, shell=True)
@pyqtSlot()
def press_btn3(self):
command3 = "test.bat"
path = "./"
self.worker.run_command(command3, cwd=path, shell=True)
@pyqtSlot(str)
def logging(self, string):
self.result.append(string.strip())
if __name__ == "__main__":
APP = QApplication(sys.argv)
ex = TestUI()
ex.show()
sys.exit(APP.exec_())
這篇關于使用 PyQt5 運行命令并獲取標準輸出和標準錯誤的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!