Datenübergabe an STDIN eines neuen Prozesses

Code-Stücke können hier veröffentlicht werden.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Dienstag 17. Januar 2006, 21:25

Hi!

Weil die Frage aufgetaucht ist, wie man über STDOUT und STDIN Daten von einem Prozess zum anderen schicken kann...

Hier eine mögliche Lösung, die mit **cPickle** arbeitet.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
"""
sender.py

Demonstriert die Datenübergaben eines gepickelten Dictionaries an STDIN
eines anderen Prozesses.
"""

import cPickle
import subprocess
import os
import os.path
import sys


#----------------------------------------------------------------------
def main():
    """
    Stellt ein Dictionary zusammen, das zuerst in einen String
    gepickelt und dann an den STDIN des aufgerufenen Programmes
    übergeben wird.
    """
   
    # Dictionary zusammenstellen
    mydict = {
        "vorname": "Gerold",
        "nachname": "Penz",
        "ort": "Oberhofen im Inntal",
    }
   
    # Dictionary in einen String pickeln
    datastring = cPickle.dumps(mydict, 2)
   
    # Prozess aufrufen
    proc = subprocess.Popen(
        (sys.executable, "empfaenger.py"),
        stdin = subprocess.PIPE,
        stdout = subprocess.PIPE,
        cwd = os.curdir
    )
   
    # In den STDIN des neuen Prozesses wird der Datenstring geschrieben
    proc.stdin.write(datastring)
    # STDIN des neuen Prozesses schließen, damit dieser weiter machen kann.
    proc.stdin.close()
   
    # STDOUT des neuen Prozesses auslesen und anzeigen
    print "Vom Sender empfangen und geschrieben:"
    print proc.stdout.read()
    # STDOUT des neuen Prozesses schließen
    proc.stdout.close()
   
   
#----------------------------------------------------------------------
if __name__ == "__main__":
    main() 

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
"""
empfaenger.py

Demonstriert die Datenübernahme eines gepickelten Dictionaries von STDIN.
"""

import cPickle
import sys


#----------------------------------------------------------------------
def main():
    datastring = sys.stdin.read()
    mydict = cPickle.loads(datastring)
    print mydict

    
#----------------------------------------------------------------------
if __name__ == "__main__":
    main()
mfg
Gerold
:-)

EDIT: WARNUNG!!! Dieses Beispiel funktioniert nicht im IDLE.
Endlich mal ein funktionierendes Beispiel eines Programms, das nicht unter Idle läuft. 8)
Getestet unter Windows mit Idle 1.1.1.

EDIT2: Statt dem hart gecodeten Pfad zu Python wird jetzt "sys.executable" verwendet. Damit dürfte es unter Linux und Windows, auch bei verschiedenen Python-Installationen, funktionieren.
Zuletzt geändert von gerold am Freitag 20. Januar 2006, 17:14, insgesamt 4-mal geändert.
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Dienstag 17. Januar 2006, 22:17

Das geht sogar noch ein bissel anders:

sender.py

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
"""
sender.py

Demonstriert die Datenübergaben eines gepickelten Dictionaries an STDIN
eines anderen Prozesses.
"""

import cPickle
import subprocess
import os


#----------------------------------------------------------------------
def main():
    """
    Stellt ein Dictionary zusammen, das zuerst in einen String
    gepickelt und dann an den STDIN des aufgerufenen Programmes
    übergeben wird.
    """
   
    # Dictionary zusammenstellen
    mydict = {
        "vorname": "Gerold",
        "nachname": "Penz",
        "ort": "Oberhofen im Inntal",
    }
   
    # Prozess aufrufen
    proc = subprocess.Popen(
        ("/usr/bin/python", "empfaenger.py",),
        stdin = subprocess.PIPE,
        stdout = subprocess.PIPE,
        cwd = os.curdir
    )
   
    # In den STDIN des neuen Prozesses wird der Datenstring geschrieben
    cPickle.dump(mydict,proc.stdin)
    tmpdict = cPickle.load(proc.stdout)
    print "Unser dict:", mydict
    print "Vom Kind zurückbekommen:", tmpdict

    cPickle.dump("hallo, wie gehts?",proc.stdin)
    tmpstr = cPickle.load(proc.stdout)
    print "Vom Kind gesagt:", tmpstr

    # Weitere Daten einzeln an den Kind-Prozess schicken und auf
    # Antwort warten.
    # ...

    # Wir sind fertig mit dem Daten schicken, Kind-Prozess signalisieren.
    # Wenn wir eine fixe Menge an Daten übertragen brauchen wir das dem
    # Kindprozess nicht zu signalisieren, wir müssen dann nur am Ende
    # stdin auf jeden Fall zumachen.
    proc.stdin.close()

    # STDOUT des neuen Prozesses schließen
    proc.stdout.close()
   
   
#----------------------------------------------------------------------
if __name__ == "__main__":
    main()
empfaenger.py

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
"""
empfaenger.py

Demonstriert die Datenübernahme eines gepickelten Dictionaries von STDIN.
"""

import cPickle
import sys


#----------------------------------------------------------------------
def main():
    while True:
        try:
            mydata = cPickle.load(sys.stdin)
        except:
            # Es konnten keine Daten mehr gelesen werden weil der Parent
            # zugemacht hat. Müßte ein EOFError sein, der hier kommt, weiß
            # ich aber nicht genau, deswegen das generelle except, was nicht
            # ganz in Ordnung ist...
            break
        cPickle.dump(mydata,sys.stdout)
    # Der Parent hat zugemacht. Finalisieren.

   
#----------------------------------------------------------------------
if __name__ == "__main__":
    main()
Die Idee hinter dem ganzen ist dass dump() und load() des (c)Pickle-Moduls nur soweit lesen wie es sich aus dem Dump ergibt, also nicht blockieren wenn der Pickle in Ordnung ist (wovon wir ja mal ausgehen). Ich kann also einen Pickle über die Leitung schieben, und dann den Pickle ohne dass ich die Verbindung zumachen muss wieder über die entgegengesetzte Funktion lesen.

Das eignet sich zum Beispiel auch zur Bidirektionalen Kommunikation, da auch der Client auf stdout etwas per Pickle an den Server zurückschicken kann, so dass dann immer weiter kommuniziert werden kann. Netter Nebeneffekt: wenn keine Daten vorhanden sind blockiert load(), so dass der jeweilige Prozess auf den anderen warten kann indem er einfach load(<pipe>) aufruft.

Erst wenn die Bidirektionale Verbindung nicht mehr gebraucht ist sollte das per schließen von stdin signalisiert werden, und dann im jeweils anderen Prozess dazu führen dass halt die Verbindung abgebaut wird.

Ich sag dazu dass der obige code untested ist; er sollte aber auf jeden Fall so gehen wir da dargestellt.

--- Heiko.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Freitag 20. Januar 2006, 16:22

modelnine hat geschrieben:Das geht sogar noch ein bissel anders:
Hi Heiko!

Ich habe es nicht geschafft, dein Beispiel zum Laufen zu bekommen. Hast du es schon einmal ausprobiert?

lg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Freitag 20. Januar 2006, 17:17

Das obige hab ich wie gesagt nicht getestet, aber ich hab was sehr ähnliches als pseudo-IPC benutzt... Ich setz mich gleich noch mal ran, mal gucken, und poste dann das was tut.

--- Heiko.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Dienstag 24. Januar 2006, 12:46

modelnine hat geschrieben:Das obige hab ich wie gesagt nicht getestet, aber ich hab was sehr ähnliches als pseudo-IPC benutzt...
Hi Heiko!

Ich habe es jetzt auch schon mit `proc.stdin.flush()` probiert, aber es funktioniert nicht unter Windows. Der Code wird bei

Code: Alles auswählen

tmpdict = cPickle.load(proc.stdout)
blockiert. Was muss ich ändern, dass es funktioniert? Was muss gesendet werden, damit der Empfänger weiß, dass er ab jetzt arbeiten soll? Was braucht es, dass der Sender weiß, dass er ab jetzt die vom Empfänger empfangenen Daten auswerten soll?

Wenn dein Beispiel zum Laufen überredet werden kann, dann wäre das wirklich eine einfach einzusetzende bidirektionale Kommunikation zwischen einzelnen Prozessen. Man bräuchte keine eigenständig laufenden Serverprogramme, man muss keine Ports bei der lokalen Firewall frei schalten, usw.

lg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Dienstag 24. Januar 2006, 12:54

Hi Heiko!

Wenn ich wieder ein wenig Zeit habe, dann probiere ich es auf eine andere Art aus. Vielleicht ist der Empfänger dazu zu bewegen, immer nur ein Byte zu lesen. Dann sammle ich die einzelnen Bytes bis ein Abbruchsignal übermittelt wird. Dann schiebe ich den gesammelten Bytestring in ein Pickle-Objekt, arbeite damit, gebe wieder Daten zurück und fange wieder mit Bytesammeln an.

Das probiere ich in ein paar Tagen aus. Vielleicht geht es ja auf diese Art.

lg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
jens
Moderator
Beiträge: 8482
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Dienstag 24. Januar 2006, 13:09

Ist es nicht evtl. das selbe Problem was ich hier http://www.python-forum.de/viewtopic.php?p=29568#29568 geschildert hab???

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Dienstag 24. Januar 2006, 13:11

Humm... Es kann sein dass es unter Windows Probleme damit gibt weil stdin gebuffered ist. Unter Unix hab ich keinerlei Probleme damit. Kleines Beispiel:

Folgende Ausgabe wird produziert:

Code: Alles auswählen

modelnine@phoenix ~ $ python test2.py
Subprocess started!
Writing data to subprocess: ['test', '123', 1234]
Received in subprocess: ['test', '123', 1234]
Got data from subprocess: ['test', '123', 1234]
Writing data to subprocess: ['test', '123', 1234]
Received in subprocess: ['test', '123', 1234]
Got data from subprocess: ['test', '123', 1234]
Writing data to subprocess: ['test', '123', 1234]
Received in subprocess: ['test', '123', 1234]
Got data from subprocess: ['test', '123', 1234]
Writing data to subprocess: ['test', '123', 1234]
Received in subprocess: ['test', '123', 1234]
Got data from subprocess: ['test', '123', 1234]
Writing data to subprocess: ['test', '123', 1234]
Received in subprocess: ['test', '123', 1234]
Got data from subprocess: ['test', '123', 1234]
Writing data to subprocess: ['test', '123', 1234]
Received in subprocess: ['test', '123', 1234]
Got data from subprocess: ['test', '123', 1234]
Writing data to subprocess: ['test', '123', 1234]
Received in subprocess: ['test', '123', 1234]
Got data from subprocess: ['test', '123', 1234]
Writing data to subprocess: ['test', '123', 1234]
Received in subprocess: ['test', '123', 1234]
Got data from subprocess: ['test', '123', 1234]
Writing data to subprocess: ['test', '123', 1234]
Received in subprocess: ['test', '123', 1234]
Got data from subprocess: ['test', '123', 1234]
Writing data to subprocess: ['test', '123', 1234]
Received in subprocess: ['test', '123', 1234]
Got data from subprocess: ['test', '123', 1234]
Closed stdin and out for subprocess. Terminating.
modelnine@phoenix ~ $ Parent has closed connection. Terminating.
von folgenden Skripten:

test1.py

Code: Alles auswählen

import pickle
import sys

# Daten lesen und gleich wieder dumpen.
sys.stderr.write("Subprocess started!\n")
while True:
    try:
        data = pickle.load(sys.stdin)
        sys.stderr.write("Received in subprocess: %r\n" % data)
    except EOFError:
        sys.stderr.write("Parent has closed connection. Terminating.\n")
        break
    pickle.dump(data,sys.stdout)
    sys.stdout.flush()
test2.py

Code: Alles auswählen

import pickle
import subprocess

proc = subprocess.Popen(["python","test1.py"],0,None,
                        subprocess.PIPE,subprocess.PIPE,None)

data = ["test","123",1234]
for i in range(10):
    print "Writing data to subprocess:", data
    pickle.dump(data,proc.stdin)
    newdata = pickle.load(proc.stdout)
    print "Got data from subprocess:", newdata

proc.stdin.close()
proc.stdout.close()

print "Closed stdin and out for subprocess. Terminating."
Probier das mal auf einer Windows-Kiste aus...

--- Heiko.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Dienstag 24. Januar 2006, 13:29

modelnine hat geschrieben:Probier das mal auf einer Windows-Kiste aus...
Hi Heiko!

Ich sehe die Meldung, kann sie aber nicht deuten. :oops:

Code: Alles auswählen

C:\Dokumente und Einstellungen\Gerold\Desktop\Neuer Ordner>test2.py
Writing data to subprocess: ['test', '123', 1234]
Subprocess started!
Received in subprocess: ['test', '123', 1234]
Traceback (most recent call last):
  File "C:\Dokumente und Einstellungen\Gerold\Desktop\Neuer Ordner\test2.py", line 11, in ?
    newdata = pickle.load(proc.stdout)
  File "C:\Python24\lib\pickle.py", line 1390, in load
    return Unpickler(file).load()
  File "C:\Python24\lib\pickle.py", line 872, in load
    dispatch[key](self)
  File "C:\Python24\lib\pickle.py", line 980, in load_string
    raise ValueError, "insecure string pickle"
ValueError: insecure string pickle
Parent has closed connection. Terminating.

C:\Dokumente und Einstellungen\Gerold\Desktop\Neuer Ordner>
Ich sehe mir das später mal an, wenn ich mehr Zeit übrig habe.

lg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
jens
Moderator
Beiträge: 8482
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Dienstag 24. Januar 2006, 13:32

gerold hat geschrieben:Ich sehe die Meldung, kann sie aber nicht deuten. :oops:

Code: Alles auswählen

ValueError: insecure string pickle
naja, das sieht so aus, also ob die übergebenen Daten nicht mehr richtig sind...
Vielleicht einfach nur ein Fehler mit den unterschiedlichen Zeilenendenzeichen?

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Dienstag 24. Januar 2006, 13:38

Humm... Das ist ein ganz seltsamer Fehler, der eingentlich nur auftritt wenn der Pickle kaputt ist (der code der das prüft guckt ob der gepickelte String mit einem " oder ' anfängt, und wenn ja, dass er auch mit dem selben Zeichen aufhört, und wenn er eine Bedingung nicht erfüllt wird der Fehler geschmissen). Liegt wahrscheinlich an universal_newlines=False (da das letzte Zeichen bei Dir mit großer Wahrscheinlichkeit ein \r ist, wegen der besch... Windows-Konvention von \r\n, und readline() was da benutzt wird nur \n wegschmeißt als Separator).

Probier mal beim Konstruieren des subprocess-Objekts ein universal_newlines=True zu setzen, und schau dann ob es geht. Sonst: Du kannst natürlich auch probieren einen binären Pickle über die Leitung zu schieben; das sollte eigentlich auch gehen, unabhängig von universal_newlines.

Dazu gibst Du dem pickle.dump-Aufruf protocol=pickle.HIGHEST_PROTOCOL mit.

--- Heiko.
Benutzeravatar
jens
Moderator
Beiträge: 8482
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Dienstag 24. Januar 2006, 13:54

Ich hab es mal jetzt getestet und es kommt genau zu dem Fehler den gerold geschrieben hat... Mit universal_newlines=True hab ich es auch Probiert, bringt aber anscheinend auch nicht viel...

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Dienstag 24. Januar 2006, 13:55

Im Endeffekt liegts daran dass stdin mit mode "w" geöffnet wird. Bedeutet also dass die bescheuerte Interpretierung von Zeilenenden \n -> \r\n von Windows gemacht wird. Man kann aber bei subprocess nicht einstellen dass die Pipe binary sein soll, was irgendwie extrem nervt.

Mal schauen. Ich weiß warum ich Windows nicht mag. Jeden Tag wieder.

--- Heiko.
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Dienstag 24. Januar 2006, 14:01

Lala... Folgendes sollte tun:

test1.py

Code: Alles auswählen

import pickle
import sys

# Daten lesen und gleich wieder dumpen.
sys.stderr.write("Subprocess started!\n")
while True:
    try:
        data = pickle.load(sys.stdin)
        sys.stderr.write("Received in subprocess: %r\n" % data)
    except EOFError:
        sys.stderr.write("Parent has closed connection. Terminating.\n")
        break
    pickle.dump(data,sys.stdout)
    sys.stdout.flush()
test2.py

Code: Alles auswählen

import pickle
import subprocess

proc = subprocess.Popen(["python","-u","test1.py"],0,None,
                        subprocess.PIPE,subprocess.PIPE,None)

data = ["test","123",1234]
for i in range(10):
    print "Writing data to subprocess:", data
    pickle.dump(data,proc.stdin)
    newdata = pickle.load(proc.stdout)
    print "Got data from subprocess:", newdata

proc.stdin.close()
proc.stdout.close()

print "Closed stdin and out for subprocess. Terminating."
--- Heiko.
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Dienstag 24. Januar 2006, 14:08

Informationen dazu gibts unter:

http://www.python.org/doc/faq/windows.h ... t-or-win95

--- Heiko.
Antworten