@JakobDev: Anmerkungen zum Quelltext:
Sternchen-Importe sind Böse™. Aus `QWidgets` importierst man auf diese Weise etwas mehr als 200(!) Namen von denen nur ein ganz kleiner Bruchteil verwendet wird.
Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.
Nach Kommas erhöht ein Leerzeichen die Lesbarkeit, genau wie in normalem Text.
Die `setup()`-Methode ist komisch bis falsch. Ein Objekt sollte nach der Initialisierung in aller Regel komplett sein und benutzbar sein. Das in das Erstellen des Objekts und eine separat aufrufbare öffentliche `setup()`-Methode aufzuteilen die man zudem noch zwingend immer nach dem erstellen aufrufen muss, macht keinen Sinn. Nur mehr Arbeit und bietet Angriffsfläche für Fehler.
Wobei der `show()`-Aufruf dann nicht mehr in die `__init__()` gehört, denn damit nimmt man dem Code der das Widget benutzt, die Möglichkeit zu entscheiden was er damit machen will. Kann ja beispielsweise sein, das man sich irgendwann einmal entscheidet das `TabWindow` nicht als Fenster zu verwenden, sondern da noch ein Hauptfenster drumherum basteln will/muss. Da ist der Name `TabWindow` dann auch nicht besonders hilfreich.
`GameOutputTab` ist auch als Name ein bisschen falsch, denn es ist ja nur ein Tab wenn man es als Tab einem `QTabWidget` hinzufügt. Das muss man aber nicht machen.
Die `run()`-Methode ist keine Methode sondern einfach nur eine Funktion. Als solche sollte sie vielleicht nicht in der Klasse stecken.
``shell=True`` sollte man nicht verwenden. Dein Code enthält nichts was das benötigen würde, dafür fängt man sich aber die Probleme ein die `os.system()` und die `os.popen*()`-Funktionen haben. Genau deswegen wurde das `subprocess`-Modul geschrieben, damit man da eben keine unnötige Shell zwischen dem eigenen Programm und dem externen Programm hat. Auch die `QProcess`-Klasse hat so eine Methode von der auch dort in der Dokumentation dringend abgeraten wird.
Die ``while True:``-Schleife ist ungewöhnlich – da würde man eher mit einer ``for``-Schleife über `process.stdout` iterieren. Die `readline()`-Methode braucht man eigentlich nie.
Und dann passiert in dieser Schleife etwas eher komisches von dem ich nicht weiss ob das so sein soll, oder ob Du da einen Fehler gemacht hast: Die Schleife bricht bei der ersten Leerzeile ab! Nicht wenn da nichts mehr vom externen Prozess kommt, sondern wenn da eine *Leerzeile* kommt!
Was soll denn da mit dem dem Prozess passieren? Um den sauber abzuräumen muss man am Ende ja den Rückgabecode abfragen, damit das kein Zombieprozess wird. Aber wenn eine Leerzeile kommt, dann muss der Prozess ja noch gar nicht fertig sein, man müsste also auf das Ende des Prozesses warten. Oder das Ende aktiv herbeiführen? Was soll denn da passieren in dem Fall?
Ausserdem sollte man an der Stelle wo man den Generator konsumiert dafür sorgen, dass er in jedem Fall geschlossen wird, damit Aufräumarbeiten im Generator auch auf jeden Fall ausgeführt werden.
Man kann das was da ausgeführt wird, also…
Code: Alles auswählen
for line in process.stdout:
line = line.rstrip()
if not line:
break
yield line
… mit `itertools.takewhile()` auch wesentlich kompakter ausdrücken:
Code: Alles auswählen
yield from takewhile(bool, (line.rstrip() for line in process.stdout))
Zwischenstand (ungetestet):
Code: Alles auswählen
#!/usr/bin/env python3
from contextlib import closing
from itertools import takewhile
import subprocess
import sys
from PyQt5.QtWidgets import (
QApplication, QPlainTextEdit, QPushButton, QTabWidget, QWidget
)
def run(command):
process = subprocess.Popen(
command, stdout=subprocess.PIPE, universal_newlines=True
)
try:
yield from takewhile(bool, (line.rstrip() for line in process.stdout))
finally:
#
# TODO Nach der Leerzeile oder wenn ein Problem auftritt, wird
# der Prozess beendet und der Rückgabecode abgefragt.
# Sollte an dieser Stelle etwas anderes passieren…
#
process.terminate()
process.wait()
class GameOutput(QPlainTextEdit):
def execute_command(self, command):
with closing(run(command)) as lines:
for line in lines:
self.appendPlainText(line)
class Tabs(QTabWidget):
def __init__(self, parent=None):
QTabWidget.__init__(self, parent)
widget = QWidget()
button = QPushButton('Klick mich!', widget)
button.clicked.connect(self.run_game)
self.addTab(widget, 'General')
def run_game(self):
output = GameOutput()
self.setCurrentIndex(self.addTab(output, 'Output'))
output.execute_command(['ping', '-c', '5', 'example.com'])
def main():
app = QApplication(sys.argv)
tabs = Tabs()
tabs.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Der Code macht immer noch das was Deiner macht, hat also auch noch das Problem mit der blockierten GUI. Wenn man das mit `subprocess` lösen will, müsste man mit einem Thread arbeiten und von dem aus dann die einzelnen gelesenen Zeilen mit einem Signal an den GUI-Thread übermitteln.