【发布时间】:2019-08-08 20:48:42
【问题描述】:
我有一个脚本可以激活虚拟环境并在其中运行pip 命令。为此,我首先使用所需的命令创建一个bash 脚本,并将最终命令(最终运行脚本)传递给run_script(),它会逐行生成输出。子进程可以正常工作,并且可以将输出打印到控制台。
现在,我想要实现的是显示捕获的run_script() 的实时输出(逐行显示)(显示pip install ... 的安装进度)以及@ 中的QProgressBar 987654327@.
到目前为止,我尝试在ProgBarDialog 类中设置self.statusLabel 的文本,但这并没有按预期工作。我想我可以创建一个类似的循环
for line in output:
self.statusLabel.setText(line)
并依次显示进程输出的每一行。但是我不知道如何准确地从输出中捕获每一行,因为输出是一个大字符串,因此,for line in output 当然会捕获字符而不是行。
如何操作输出以正确格式化输出,以便能够在 QDialog 内的小部件(例如 QLabel 或类似的东西)中显示它?
(可能是我的编码方式愚蠢或低效,因此欢迎提出任何建议)
最小可重现示例:
注意:需要testfile.py旁边的虚拟环境才能重现。
testfile.py
from subprocess import Popen, PIPE
import sys
import os
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QApplication, QDialog, QVBoxLayout,
QHBoxLayout, QLabel, QProgressBar)
def has_bash():
"""
Test if bash is available. If present the string `/bin/bash` is returned,
an empty string otherwise.
"""
res = Popen(
["which", "bash"], stdout=PIPE, stderr=PIPE, text="utf-8"
)
out, _ = res.communicate()
shell = out.strip()
return shell
def run_script(command):
"""
Run the script and catch output of the subprocess line by line.
The `command` argument is set in `run_pip()`.
"""
process = Popen(command, stdout=PIPE, text="utf-8")
while True:
output = process.stdout.readline()
if output == "" and process.poll() is not None:
break
if output:
# TODO: show output in dialog together with a progressbar
print(f"[PIP]: {output.strip()}")
rc = process.poll()
return rc
def run_pip(cmd, opt, package, venv_dir, venv_name):
"""
Activate the virtual environment and run pip commands.
"""
current_dir = os.path.dirname(os.path.realpath(__file__))
script = os.path.join(current_dir, "run.sh")
if has_bash():
# create run script
with open(script, "w") as f:
f.write(
"#!/bin/bash\n"
f"source {venv_dir}/{venv_name}/bin/activate\n"
f"pip {cmd}{opt}{package}\n"
"deactivate\n"
)
# make it executable
os.system(f"chmod +x {script}")
# run script
command = ["/bin/bash", script]
run_script(command)
class ProgBarDialog(QDialog):
"""
Dialog showing output and a progress bar during the installation process.
"""
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(675, 365, 325, 80)
self.setFixedSize(350, 85)
self.setWindowFlag(Qt.WindowCloseButtonHint, False)
self.setWindowFlag(Qt.WindowMinimizeButtonHint, False)
h_Layout = QHBoxLayout(self)
v_Layout = QVBoxLayout()
h_Layout.setContentsMargins(0, 15, 0, 0)
self.statusLabel = QLabel(self)
self.placeHolder = QLabel(self)
self.progressBar = QProgressBar(self)
self.progressBar.setFixedSize(325, 23)
self.progressBar.setRange(0, 0)
v_Layout.addWidget(self.statusLabel)
v_Layout.addWidget(self.progressBar)
v_Layout.addWidget(self.placeHolder)
h_Layout.addLayout(v_Layout)
self.setLayout(h_Layout)
if __name__ == "__main__":
cmd = ["install "]
opt = ["--upgrade "]
package = "pylint" # this could be any package
current_dir = os.path.dirname(os.path.realpath(__file__))
venv_name = "testenv" # a virtual env beside this test file
run_pip(cmd[0], opt[0], package, current_dir, venv_name)
#]=======================================================================[#
app = QApplication(sys.argv)
progBar = ProgBarDialog()
progBar.show()
sys.exit(app.exec_())
【问题讨论】:
标签: python python-3.x pyqt subprocess pyqt5