subprocess statt os

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
midan23
User
Beiträge: 147
Registriert: Sonntag 21. Mai 2006, 21:41
Wohnort: Müchen
Kontaktdaten:

Hallo zusammen ...

Ich hätte da ein kleines Problem ... wieder mal ...

In folgendem Code:

Code: Alles auswählen

class MyPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, wx.ID_ANY)

        self.output = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_MULTILINE)
        self.button = wx.Button(self, wx.ID_ANY, "Start")
        self.button.Bind(wx.EVT_BUTTON, self.OnClick)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.output, 1, wx.EXPAND)
        sizer.Add(self.button)
        self.SetSizer(sizer)

    def OnClick(self, event):
        self.output.Value = ""
        self.button.Disable()
        out = os.popen(COMMAND, "r")
        while True:
            line = out.readline()
            if line:
                self.appendText(line)
            else:
                break
        self.button.Enable()

    def add_text(self, txt):
        self.output.AppendText(txt)
möchte ich subprocess statt os verwenden ... ich weiss nur nicht, wie ich das anstellen soll.
Laut Doku soll man zwar statt

Code: Alles auswählen

pipe = os.popen(cmd, mode='r', bufsize)

Code: Alles auswählen

 pipe = Popen(cmd, shell=True, bufsize=bufsize, stdout=PIPE).stdout
verwenden, aber irgendwie haut das nicht so hin ... es wird nähmlich gar nichts angezeigt ...

Wäre für Anregungen dankbar ...
midan23
User
Beiträge: 147
Registriert: Sonntag 21. Mai 2006, 21:41
Wohnort: Müchen
Kontaktdaten:

Und selber was gefunden ... aber mit wx.Process und wx.Execute:

Code: Alles auswählen

COMMAND = r"/bin/bash test.sh"

class MyPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, wx.ID_ANY)

        self.process = None

        self.output = wx.TextCtrl(self, wx.ID_ANY, style = wx.TE_MULTILINE)
        self.button = wx.Button(self, wx.ID_ANY, "Start")
        self.button.Bind(wx.EVT_BUTTON, self.OnClick)

        self.Bind(wx.EVT_END_PROCESS, self.OnProcessEnded)
        self.Bind(wx.EVT_IDLE, self.OnIdle)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.output, 1, wx.EXPAND)
        sizer.Add(self.button)
        self.SetSizer(sizer)

    def __del__(self):
        if self.process is not None:
            self.process.Detach()
            self.process.CloseOutput()
            self.process = None

    def OnClick(self, event):
        self.output.Value = "Starting '%s'\n" % COMMAND
        self.button.Disable()
        self.process = wx.Process(self)
        self.process.Redirect()
        wx.Execute(COMMAND, wx.EXEC_ASYNC, self.process)

    def OnProcessEnded(self, event):
        self.append_text()
        self.process.Destroy()
        self.process = None

        self.button.Enable()

    def OnIdle(self, event):
        if self.process is not None:
            self.append_text()

    def append_text(self):
        stream = self.process.GetInputStream()
        if stream.CanRead():
            self.output.AppendText(stream.read())
Hat etwas länger gedauert ... die Doku schweigt sich über wx.Process und wx.Execute aus ... Zum Glück gibt es die Demo, das Beispiel hat sehr geholfen ...
BlackJack

Vielleicht schweigt sich die Doku darüber aus, weil man lieber die Möglichkeiten der Standardbibliothek benutzen sollte.
midan23
User
Beiträge: 147
Registriert: Sonntag 21. Mai 2006, 21:41
Wohnort: Müchen
Kontaktdaten:

BlackJack hat geschrieben:Vielleicht schweigt sich die Doku darüber aus, weil man lieber die Möglichkeiten der Standardbibliothek benutzen sollte.
Das kann sein ... wenigstens funktioniert es und sieht etwas besser aus als mit os ...

Mein Ziel bleibt allerdings die Verwendung von subprocess.

Das heisst: Vorschläge sind willkommen ...
midan23
User
Beiträge: 147
Registriert: Sonntag 21. Mai 2006, 21:41
Wohnort: Müchen
Kontaktdaten:

Kann mir mal jemand erklären, warum folgender Code

Code: Alles auswählen

cmd = r"d:\fg\t.cmd"
process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
out = process.stdout
for line in out.readlines():
    self.output.AppendText(line)
interaktiv eingegeben funktioniert, aber als Methode einer von wx.Panel abgeleiteten Klasse zu diesem Fehler führt:

Code: Alles auswählen

Traceback (most recent call last):

  File "C:\Dokumente und Einstellungen\gobin\Desktop\test.pyw", line 79, in <module>

    main()

  File "C:\Dokumente und Einstellungen\gobin\Desktop\test.pyw", line 75, in main

    app = MyApp(filename="error.txt")

  File "D:\fg\python\Lib\site-packages\wx-2.8-msw-unicode\wx\_core.py", line 7757, in __init__

    self._BootstrapApp()

  File "D:\fg\python\Lib\site-packages\wx-2.8-msw-unicode\wx\_core.py", line 7354, in _BootstrapApp

    return _core_.PyApp__BootstrapApp(*args, **kwargs)

  File "C:\Dokumente und Einstellungen\gobin\Desktop\test.pyw", line 70, in OnInit

    frame = MyFrame()

  File "C:\Dokumente und Einstellungen\gobin\Desktop\test.pyw", line 65, in __init__

    e.execute(r"d:\fg\t.cmd")

  File "C:\Dokumente und Einstellungen\gobin\Desktop\test.pyw", line 35, in execute

    process = subprocess.Popen(cmd, stdout=subprocess.PIPE)

  File "D:\fg\python\lib\subprocess.py", line 586, in __init__

    errread, errwrite) = self._get_handles(stdin, stdout, stderr)

  File "D:\fg\python\lib\subprocess.py", line 699, in _get_handles

    p2cread = self._make_inheritable(p2cread)

  File "D:\fg\python\lib\subprocess.py", line 744, in _make_inheritable

    DUPLICATE_SAME_ACCESS)

WindowsError: [Error 6] Das Handle ist ungültig
Ich verstehe es zumindest nicht ...
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

Da gibt es vermutlich Rangelei bzgl. stdin/-out/-err. Eine Email scheint das zu bestätigen:
>> >> I get an OSError exception when I try:
>> >>
>> >> prog = subprocess.Popen(command, stdout=subprocess.PIPE)
>> >>
>> >> with the message: '[Error 9] The handle is invalid'

[...]

This is probably the usual subprocess bug when the script runs
in a GUI process (no console) on Windows. The OP should try
to run the unfrozen script with pythonw.exe instead of python.exe;
I assume the same problem occurs then. The problem is that subprocess
tries to duplicate the handles for stdin, stdout, and stderr, and this
fails because the handles are already closed in a gui process.

IIRC the workaround is to connect stdin and stderr also to PIPE in the
subprocess call.
midan23
User
Beiträge: 147
Registriert: Sonntag 21. Mai 2006, 21:41
Wohnort: Müchen
Kontaktdaten:

Kann sein ... aber ich bin auch aus einem anderen Grund mit meiner subprocess-Lösung unzufrieden:

Das Fenster mit der Ausgabe des Befehls erscheint erst, nachdem der Befehl fertig ist ...

Ich möchte aber, das die AUsgaben des Befehls im Fenster erscheinen während der Befehl läuft ...
(Sozusagen als Fortschrittsanzeige ...)

Ist nicht ganz das wahre, ein Programm zu starten und ohne weitere Hinweise erscheint das Fenster erst ein paar Minuten später ...
midan23
User
Beiträge: 147
Registriert: Sonntag 21. Mai 2006, 21:41
Wohnort: Müchen
Kontaktdaten:

So ... hier mal eine Lösung mit subprocess:

Code: Alles auswählen

class ExecPanel(wx.Panel):

    def __init__(self, parent):
        wx.Panel.__init__(self, parent, wx.ID_ANY)

        self.process = None

        self.output = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_MULTILINE|wx.TE_READONLY)

        self.Bind(wx.EVT_IDLE, self.OnIdle)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.output, 1, wx.EXPAND)
        
        self.SetSizer(sizer)

    def execute(self, cmd):
        self.output.Value = "Starting '%s'\n" % cmd
        self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE)

    def OnIdle(self, event):
        out = self.process.stdout
        line = out.readline()
        if line:
            self.output.AppendText(line)
Aber wirklich zufrieden bin ich damit auch nicht ...
  • Wenn das Fenster inaktiv ist, tut sich gar nichts im Textfeld
  • Wenn das Fenster aktiv ist, werden die Zeilen ungefähr im Sekunden-Rythmus ausgegeben
  • Wenn ich den Mauszeiger im Fenster bewege (egal ob aktiv oder inaktiv) tut sich richtig was ... aber nur, so lange der Mauszeiger bewegt wird ...
Mir scheint wx.EVT_IDLE ist der falsche Platz, um die Anzeige zu aktualisieren ...
Was gäbe es denn für Alternativen ?
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

In +/- jedem mir bekannten GUI System ist es möglich Timer in die EventQueue des Mainloop einzufügen. Mit wx kenne ich mich leider nicht wirklich aus. Das dürfte aber sein was du suchst: http://wiki.wxpython.org/Timer

Gruss,
Jonas
midan23
User
Beiträge: 147
Registriert: Sonntag 21. Mai 2006, 21:41
Wohnort: Müchen
Kontaktdaten:

wx.Timer ... hab ich auch schon drüber nachgedacht ...
Im Vergleich zur wx.EVT_IDLE würde das zwar dafür sorgen, das die Ausgabe regelmässiger und schneller aktualisiert wird, aber so aktuell wie ein separates DOS-Fenster ist es wahrscheinlich nicht ...

Ich versuchs mal ...
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

midan23 hat geschrieben:wx.Timer ... hab ich auch schon drüber nachgedacht ...
Im Vergleich zur wx.EVT_IDLE würde das zwar dafür sorgen, das die Ausgabe regelmässiger und schneller aktualisiert wird, aber so aktuell wie ein separates DOS-Fenster ist es wahrscheinlich nicht ...

Ich versuchs mal ...
Hängt vermutlich stark davon ab wie häufig du pollst. Schade? das Wintendo kein select() auf Pipes unterstützt :roll:
Ist es mit subprocess möglich mit Synchronem IO zu arbeiten? Wenn ja könntest du das ganze auch mit Threads lösen. Der Thread kann dann wenn eine neue Zeile gelesen wurde diese per wx.PostEvent an den Hauptthread weiterleiten.

Gruss,
Jonas
midan23
User
Beiträge: 147
Registriert: Sonntag 21. Mai 2006, 21:41
Wohnort: Müchen
Kontaktdaten:

Also, mit wx.Timer geht es halbwegs:
Ich starte den Timer mit

Code: Alles auswählen

self.timer.Start(10)
und alles läuft wie gewünscht.

Noch eine Frage: Reicht es, wenn der Timer so

Code: Alles auswählen

def OnTimer(self, event):
    out = self.process.stdout
    line = out.readline()
    if line:
        self.output.AppendText(line)
    else:
        self.timer.Stop()
gestoppt wird ?
Antworten