Moin zusammen!
Ich eröffne zu meinen aktuellen 2 Probleme 2 unterschiedliche Threads, ich hoffe, das ist richtig so.
Wenn ich einen Befehl mit subprozess.run() starte, bekomme ich die Ausgabe irgendwie in ein Textfeld?
Die Ui habe ich mit dem Qt-Creator erstellt, da muss es doch sicher eine Möglichkeit geben ein Fenster auszuwählen, und die Ausgabe "da rein zu bekommen".
Edit:
https://stackoverflow.com/questions/167 ... -qt-widget
Das klingt vielversprechend, aber ich bekomme das im Kopf nicht verknüpft.
Ausgabe von subprozess.run() in Textfenster umleiten
Ich habe die letzten Tage damit verbracht, Nicht zu komplizierten Beispielcode zu finden. Unter anderen auch den folgenden.
Nur funktioniert schon das Beispiel nicht. Ich setzte wie gesagt Python 3.7 und Qt5 ein.
Vielleicht findet einer von euch den Fehler, dann kann ich versuchen das Beispiel auf meinen Code zu übertragen.
Nur funktioniert schon das Beispiel nicht. Ich setzte wie gesagt Python 3.7 und Qt5 ein.
Vielleicht findet einer von euch den Fehler, dann kann ich versuchen das Beispiel auf meinen Code zu übertragen.
Code: Alles auswählen
# ref to https://www.saltycrane.com/blog/2007/12/pyqt-example-how-to-run-command-and/
import os
import sys
import subprocess
from PyQt5 import QtWidgets
def main():
app = QtWidgets.QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
class MyWindow(QtWidgets.QWidget):
def __init__(self, *args):
QtWidgets.QWidget.__init__(self, *args)
# create objects
label = QtWidgets.QLabel(self.tr("Enter command and press Return"))
self.le = QtWidgets.QLineEdit()
self.te = QtWidgets.QTextEdit()
# layout
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(label)
layout.addWidget(self.le)
layout.addWidget(self.te)
self.setLayout(layout)
# create connection
self.le.returnPressed.connect(self.run_command)
def run_command(self):
cmd = str(self.le.text())
stdouterr = subprocess.run(cmd)[1].read()
self.te.setText(stdouterr)
if __name__ == "__main__":
main()
- __blackjack__
- User
- Beiträge: 13112
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@Dirki: Starte das doch mal aus einem Terminal heraus, dann wird der der Fehler angezeigt, und der ist ziemlich eindeutig:
Wie bist Du denn auf die Idee gekommen das man bei `CompletedProcess`-Objekten per Index auf irgend etwas zugreifen kann?
Als nächstes hast Du soweit ich das sehe Glück das man `run()` *so* auch mit einer Zeichenkette aufrufen kann, denn eigentlich wird da eine Liste erwartet. Dann ist die Frage wovon Du eigentlich lesen möchtest – da gibt's nichts wo man `read()` aufrufen könnte. Die Ausgabe selbst könnte man vom `CompletedProcess` abfragen *wenn* man bei `run()` angegeben hätte, das man das haben möchte. Hast Du Dir die Dokumentation von `run()`/`subprocess()` denn überhaupt mal angeschaut?
Code: Alles auswählen
In [20]: subprocess.run('true')[1]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-20-e6614f14330b> in <module>()
----> 1 subprocess.run('true')[1]
TypeError: 'CompletedProcess' object does not support indexing
Als nächstes hast Du soweit ich das sehe Glück das man `run()` *so* auch mit einer Zeichenkette aufrufen kann, denn eigentlich wird da eine Liste erwartet. Dann ist die Frage wovon Du eigentlich lesen möchtest – da gibt's nichts wo man `read()` aufrufen könnte. Die Ausgabe selbst könnte man vom `CompletedProcess` abfragen *wenn* man bei `run()` angegeben hätte, das man das haben möchte. Hast Du Dir die Dokumentation von `run()`/`subprocess()` denn überhaupt mal angeschaut?
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Danke für deine Antwort, BlackJack!
Ich habe den Code im Internet bei meinen Recherchen gefunden und "geliehen" um zu verstehen was da passiert. Meine Hoffnung war, das wenn ich das verstanden habe, den Code an mein Programm anzupassen.
Aktuall lese ich tatsächlich die Doku von subprocess.
Ich habe den Code im Internet bei meinen Recherchen gefunden und "geliehen" um zu verstehen was da passiert. Meine Hoffnung war, das wenn ich das verstanden habe, den Code an mein Programm anzupassen.
Aktuall lese ich tatsächlich die Doku von subprocess.
Also mit
Habe ich das so verstanden, das die Ausgabe zwischengespeichert wird, genau so eine Möglichkeit gibt es auch bei Popen:
Ich finde nur nicht den dreher, wie ich an die daten wieder ran komme. Ich weiß, du/ihr meint es gut, das ihr uns Anfängern nicht die Lösung präsentiert, sondern uns den Weg zeigt, aber kannst du (ihr) evtl ein wenig konkreter werden? Ich komm einfach nicht dahinter.
Code: Alles auswählen
subprocess.run([ydl_path, "--output", target + "/%(playlist_index)s - %(title)s.%(ext)s", "-u", uname, "-p", pwd, url], capture_output=True, check=True)
Habe ich das so verstanden, das die Ausgabe zwischengespeichert wird, genau so eine Möglichkeit gibt es auch bei Popen:
Code: Alles auswählen
subprocess.Popen([ydl_path, "--output", target + "/%(playlist_index)s - %(title)s.%(ext)s", "-u", uname, "-p", pwd, url], stdout=subprocess.PIPE)
- __blackjack__
- User
- Beiträge: 13112
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@Dirki: Gleich mal vorweg: Was Du willst ist *nicht einfach*! Denn ich nehme mal an Du willst die Ausgabe *während* das externe Programm läuft, und das externe Programm läuft nicht nur ganz kurz, sondern eine ganze Weile bis es fertig ist. Das bedeutet Du kannst das nicht einfach so in einem GUI-Rückruf machen, denn der darf nur sehr kurz irgendetwas tun, sonst blockiert die GUI, was im schlechtesten Fall dazu führen kann das der Benutzer vom System informiert wird, dass Dein Programm nicht mehr zu reagieren scheint, und ob es abgebrochen werden soll. Du brauchst also nebenläufige Programmierung um das asynchron zur GUI-Hauptschleife ausführen zu können. Dazu brauchst Du entweder einen Thread (oder zwei wenn `stderr` und `stdout` getrennt verarbeitet werden sollen) oder `QProcess`. Ich würde einen Thread verwenden, weil `QProcess` sehr unpraktisch wird wenn die Kodierung die der externe Prozess für seine Ausgaben verwendet nicht ein Byte für ein Zeichen verwendet.
Wie man an die Ausgabe(n) von `subprocess.run()` heran kommt steht doch in der Dokumentation: Die Funktion gibt etwas zurück. Das hat den Typ `CompletedProcess`. Und direkt nach der `subprocess.run()` ist Dokumentiert was für Attribute `CompletedProcess`-Objekte haben und was deren Wert jeweils ist. Falls die Ausführung des externen Programms nicht erfolgreich war, dann gibt die Funktion etwas vom Typ `CalledProcessError` zurück – das steht bei der Beschreibung vom `check`-Argument. Und was so ein Objekt für Attribute hat und was die Werte bedeuten, steht auch in der Dokumentation. Und ein Beispiel mit 'ls' ist auch auch in der Dokumentation. Man kann auch selbst mal eine interaktive Python-Shell starten, irgendein Programm ausführen was etwas ausgibt, und sich dann das Ergebnis anschauen was man da bekommt wenn man das mit ``capture_output=True`` aufruft. Dann sollte sehr schnell offensichtlich werden wo die Ausgabe landet und wie man da dann heran kommt. Als allerletzten Notnagel, wenn man mal wirklich eine totale Denkblockade hat, probiert man einfach alle vier Attribute vom `CompletedProcess` mal durch.
Bei `Popen` ist das anders, da hat man nicht die Ausgabe des Programms nachdem es durchgelaufen ist, sondern man hat auf dem `Popen`-Objekt das oder die Dateiobjekte der Standard- und/oder Standardfehlerausgabe des externen Prozesses und ist dann selbst dafür verantwortlich die auszulesen und sinnvoll und sauber auf das Ende des Prozesses zu reagieren → `wait()` aufrufen damit kein Zombie-Prozess zurückbleibt. Und auch das man das auslesen so macht, dass keine Verklemmungen („deadlocks“) entstehen können. Wenn man `stdout` und `stderr` getrennt umleitet, dann muss man die auch nebenläufig verarbeiten, also zum Beispiel einen Thread pro Dateiobjekt. Oder man leitet nur eines von beiden um, oder man leitet `stderr` nach `stdout` um und braucht dann nur noch das verarbeiten – muss beide Kanäle dann aber gleich behandeln.
Bei dem Beispiel ist ``target + '/…`` falsch: Pfade setzt man nicht mit ``+`` zusammen sondern mit `os.path.join()` oder `pathlib`.
Falls das `ydl_path` das Python-Programm `youtube_dl` meint: Da würde ich schauen ob man das überhaupt als externes Programm starten muss, denn das ist ja in Python geschrieben und per ``pip`` installierbar. Eventuell ist es also sinnvoller das Modul zu importieren und programmatisch zu verwenden.
Wie man an die Ausgabe(n) von `subprocess.run()` heran kommt steht doch in der Dokumentation: Die Funktion gibt etwas zurück. Das hat den Typ `CompletedProcess`. Und direkt nach der `subprocess.run()` ist Dokumentiert was für Attribute `CompletedProcess`-Objekte haben und was deren Wert jeweils ist. Falls die Ausführung des externen Programms nicht erfolgreich war, dann gibt die Funktion etwas vom Typ `CalledProcessError` zurück – das steht bei der Beschreibung vom `check`-Argument. Und was so ein Objekt für Attribute hat und was die Werte bedeuten, steht auch in der Dokumentation. Und ein Beispiel mit 'ls' ist auch auch in der Dokumentation. Man kann auch selbst mal eine interaktive Python-Shell starten, irgendein Programm ausführen was etwas ausgibt, und sich dann das Ergebnis anschauen was man da bekommt wenn man das mit ``capture_output=True`` aufruft. Dann sollte sehr schnell offensichtlich werden wo die Ausgabe landet und wie man da dann heran kommt. Als allerletzten Notnagel, wenn man mal wirklich eine totale Denkblockade hat, probiert man einfach alle vier Attribute vom `CompletedProcess` mal durch.
Bei `Popen` ist das anders, da hat man nicht die Ausgabe des Programms nachdem es durchgelaufen ist, sondern man hat auf dem `Popen`-Objekt das oder die Dateiobjekte der Standard- und/oder Standardfehlerausgabe des externen Prozesses und ist dann selbst dafür verantwortlich die auszulesen und sinnvoll und sauber auf das Ende des Prozesses zu reagieren → `wait()` aufrufen damit kein Zombie-Prozess zurückbleibt. Und auch das man das auslesen so macht, dass keine Verklemmungen („deadlocks“) entstehen können. Wenn man `stdout` und `stderr` getrennt umleitet, dann muss man die auch nebenläufig verarbeiten, also zum Beispiel einen Thread pro Dateiobjekt. Oder man leitet nur eines von beiden um, oder man leitet `stderr` nach `stdout` um und braucht dann nur noch das verarbeiten – muss beide Kanäle dann aber gleich behandeln.
Bei dem Beispiel ist ``target + '/…`` falsch: Pfade setzt man nicht mit ``+`` zusammen sondern mit `os.path.join()` oder `pathlib`.
Falls das `ydl_path` das Python-Programm `youtube_dl` meint: Da würde ich schauen ob man das überhaupt als externes Programm starten muss, denn das ist ja in Python geschrieben und per ``pip`` installierbar. Eventuell ist es also sinnvoller das Modul zu importieren und programmatisch zu verwenden.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Das gleiche Problem habe ich auch und deshalb bin ich mal so frei und klinke mich hier ein. Ich habe ebenfalls eine Gui mit dem Qt creator erstellt und bin gerade dabei den Rest fertig zu stellen.
Kurz zu meinem Programm:
Platform: Windows 7
Python 3.7
PyQT5
Das Programm soll die CMD-Line Version eines Import/Export Programms aufrufen und je nach Auswahl und Konfiguration in der Gui verschiedene Parameter übergeben. Es hat einen QTextEdit Bereich in dem die Ausgabe live angezeigt werden soll.
Ich habe dieses Problem wie @__blackjack__ beschrieben hat mit subprocess.Popen gelöst. Hierbei wird vom subprocess stdout und stderr "gepiped". Die leite ich dann per communicate() an stdout und stderr der Main weiter:
Für die weiterleitung von stdout an meinen QTextEdit habe ich eine Klasse erstellt und noch ein paar Funktionen in meiner QMainWindows hinzugefügt:
Soweit so gut. Die Ausgabe wird an die GUI weitergeleitet und halbwegs lesbar (leider wegen decode ohne Umlaute) ausgegeben, ABER wie @__blackjack__ erwähnte friert dadurch die GUI ein und die Ausgabe erscheint erst, nachdem der Subprocess beendet wurde. Ich bin ein absoluter Python und PYQT Neuling und komme leider zum verrecken nicht weiter und verstehe ehrlich gesagt auch nicht, warum das Fenster erst aktualisiert wird, nachdem die Schleife in die Main zurück springt. Vielleicht können wir gemeinsam versuchen eine Lösung zu finden, oder jemand hilft uns etwas auf die Sprünge
Kurz zu meinem Programm:
Platform: Windows 7
Python 3.7
PyQT5
Das Programm soll die CMD-Line Version eines Import/Export Programms aufrufen und je nach Auswahl und Konfiguration in der Gui verschiedene Parameter übergeben. Es hat einen QTextEdit Bereich in dem die Ausgabe live angezeigt werden soll.
Ich habe dieses Problem wie @__blackjack__ beschrieben hat mit subprocess.Popen gelöst. Hierbei wird vom subprocess stdout und stderr "gepiped". Die leite ich dann per communicate() an stdout und stderr der Main weiter:
Code: Alles auswählen
def importhelper(self, befehl):
with subprocess.Popen(befehl ,stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p:
output, errors = p.communicate()
lines = output.decode('iso8859-1')
print(lines)
Code: Alles auswählen
class Stream(QtCore.QObject):
newText = QtCore.pyqtSignal(str)
def write(self, text):
self.newText.emit(str(text))
Code: Alles auswählen
class ozi(QtWidgets.QMainWindow):
def onUpdateText(self, text):
cursor = self.process.textCursor()
cursor.movePosition(QtGui.QTextCursor.End)
cursor.insertText(text)
self.process.setTextCursor(cursor)
self.process.ensureCursorVisible()
def __del__(self):
sys.stdout = sys.__stdout__
def __init__(self):
super(ozi, self).__init__()
#Lade Fenser aus gui Datei
self.ui = gui.Ui_MainWindow()
self.ui.setupUi(self)
#stdout stream ausgabe
sys.stdout = Stream(newText=self.onUpdateText)
self.process = QtWidgets.QTextEdit(self.ui.tab11)
self.process.setGeometry(QtCore.QRect(10, 390, 801, 121))
self.process.setObjectName("textBrowser1")
self.process.moveCursor(QtGui.QTextCursor.Start)
self.process.ensureCursorVisible()
self.process.setLineWrapColumnOrWidth(500)
self.process.setLineWrapMode(QtWidgets.QTextEdit.FixedPixelWidth)
Du benutzt communicate. Das ist so programmiert, das es wartet bis der Prozess beendet ist. Du musst stattdessen (wie schon erwaehnt hier im Thread) run benutzen, und das zurueckgelieferte Objekt hat dann stdout/stderr Attribute. Die kannst du zB mit sowas wie dem QSocketNotifier https://doc.qt.io/qt-5/qsocketnotifier.html#details ueberwachen, wenn sich da was tut.
@OzanOs: das Umleiten von sys.stdout ist eine ganz schlechte Idee, Zum einen, weil sie nicht funktioniert und zum anderen, weil damit ein globaler Zustand geändert wird, der vielleicht sonstwo ungewollte Auswirkungen hat. sys.stdout wird ja nur für die Python-print-Ausgabe verwendet und an der Stelle, wo Du es benutzt, weißt Du ja, dass Du etwas in ein Textfeld schreiben willst, also kannst Du es dort auch direkt machen.
Ein Kodierung zu raten ist auch schlecht, weil wie Du selbst bemerkst, dann keine Umlaute funktionieren. Die verwendete Codierung ergibt sich aus den Umgebungsvariablen für das aufgerufene Programm.
Ein Kodierung zu raten ist auch schlecht, weil wie Du selbst bemerkst, dann keine Umlaute funktionieren. Die verwendete Codierung ergibt sich aus den Umgebungsvariablen für das aufgerufene Programm.
Danke erst mal für die Antworten. Ich hatte erst kürzlich wieder Gelegenheit an meinem Programm weiter zu basteln.
Mit dem QSocketNotifier habe ich es nicht zum laufen bekommen. Dann habe ich irgendwo mal gelesen, dass die Windows Kommandozeile sich nicht wie ein Socket verhält und habe die Idee verworfen.
Nichts desto trotz habe ich nach 3 Tagen verzweifeltem rumprobieren und Foren durchforsten auf anderem Wege eine Lösung gefunden und den Code etwas umgeschrieben:
Per threading verhindere ich das Blockieren der Main, dadurch friert das Fenster nicht mehr ein und die Anzeige kann sofort aktualisiert werden.
Meine Importhelper Funktion habe ich wie folgt angepasst und die richtige codierung habe ich durch den Befehl "chcp" in der Komandozeile herausgefunden:
Auf dem Weg werden sämtliche Ausgaben direkt Live im QTextEdit angezeigt und man kann während der Subprozess zu Gange ist weiterhin das Programm bedienen.
Jetzt noch meine Frage:
Wenn ich das Programm beende meckert eclipse rum, dass meine Klasse EmittinStream keine flush funktion besitzt. Gibt es noch weitere dinge auf die ich für einen sauberen Exit beachten müsste? Oder habt ihr eventuell noch Ideen zum optimieren?
Mit dem QSocketNotifier habe ich es nicht zum laufen bekommen. Dann habe ich irgendwo mal gelesen, dass die Windows Kommandozeile sich nicht wie ein Socket verhält und habe die Idee verworfen.
Nichts desto trotz habe ich nach 3 Tagen verzweifeltem rumprobieren und Foren durchforsten auf anderem Wege eine Lösung gefunden und den Code etwas umgeschrieben:
Per threading verhindere ich das Blockieren der Main, dadurch friert das Fenster nicht mehr ein und die Anzeige kann sofort aktualisiert werden.
Code: Alles auswählen
def start_task(self, test):
self.thread = threading.Thread(target=self.start_function)
self.thread.start()
Code: Alles auswählen
def importhelper(self, befehl):
p = subprocess.Popen(befehl, shell=False, stdout=subprocess.PIPE)
while True:
out = p.stdout.readline().decode("cp850", "ignore").rstrip('\r\n')
if out == '' and p.poll() != None:
break
if out != '':
print(out)
Jetzt noch meine Frage:
Wenn ich das Programm beende meckert eclipse rum, dass meine Klasse EmittinStream keine flush funktion besitzt. Gibt es noch weitere dinge auf die ich für einen sauberen Exit beachten müsste? Oder habt ihr eventuell noch Ideen zum optimieren?
Ich sehe keine Klasse EmittinStream. Kann man also wenig zu sagen. Was kritisch ist: modifizierst du den Zustand des Widgets aus dem Hintergrundthread? Dann wird das frueher oder spaeter krachen. Dafuer muss man eigentlich einen QThread und eine QueuedConnection benutzen.