Unterprozess des Python-C-Programms hängt bei „for line in iter“

Lesezeit: 5 Minuten

Benutzer-Avatar
theo-braun

Ok, also versuche ich, ein C-Programm aus einem Python-Skript auszuführen. Derzeit verwende ich ein Test-C-Programm:

#include <stdio.h>

int main() {
    while (1) {
        printf("2000\n");
        sleep(1);
    }
    return 0;
}

Um das Programm zu simulieren, das ich verwenden werde, das ständig Messwerte von einem Sensor nimmt. Dann versuche ich, die Ausgabe zu lesen (in diesem Fall "2000") aus dem C-Programm mit Unterprozess in Python:

#!usr/bin/python
import subprocess

process = subprocess.Popen("./main", stdout=subprocess.PIPE)
while True:
    for line in iter(process.stdout.readline, ''):
            print line,

aber das funktioniert nicht. Durch die Verwendung von print-Anweisungen wird die ausgeführt .Popen Linie wartet dann an for line in iter(process.stdout.readline, ''):bis ich Strg-C drücke.

Warum ist das? Dies ist genau das, was die meisten Beispiele, die ich gesehen habe, als Code haben, und dennoch wird die Datei nicht gelesen.

Gibt es eine Möglichkeit, es nur laufen zu lassen, wenn es etwas zu lesen gibt?

  • fallen while True. Einmal die for-Schleife beendet; davon bekommst du nichts process.stdout mehr.

    – jfs

    11. Dezember 2013 um 0:29 Uhr

  • Ich denke, die Antwort sollte in JF Sebastians Version geändert werden. Siehe meinen Kommentar dort warum.

    – Scheintod

    28. Dezember 2013 um 10:02 Uhr

  • Danke @Scheintod, guter Punkt.

    – theo-braun

    31. Dezember 2013 um 7:56 Uhr

Benutzer-Avatar
jfs

Es ist ein Problem mit der Blockpufferung.

Was folgt, ist eine für Ihren Fall erweiterte Version meiner Antwort auf Python: read streaming input from subprocess.communicate() question.

Korrigieren Sie den stdout-Puffer direkt im C-Programm

stdio-basierte Programme werden in der Regel zeilengepuffert, wenn sie interaktiv in einem Terminal ausgeführt werden, und blockgepuffert, wenn ihre Standardausgabe in eine Pipe umgeleitet wird. Im letzteren Fall sehen Sie keine neuen Zeilen, bis der Puffer überläuft oder geleert wird.

Um Anrufe zu vermeiden fflush() nach jedem printf() aufrufen, könnten Sie eine zeilengepufferte Ausgabe erzwingen, indem Sie ganz am Anfang ein C-Programm aufrufen:

setvbuf(stdout, (char *) NULL, _IOLBF, 0); /* make line buffered stdout */

Sobald ein Zeilenumbruch ausgegeben wird, wird in diesem Fall der Puffer geleert.

Oder beheben Sie es, ohne die Quelle des C-Programms zu ändern

Es gibt stdbuf Dienstprogramm, mit dem Sie den Pufferungstyp ändern können, ohne den Quellcode zu ändern, z.

from subprocess import Popen, PIPE

process = Popen(["stdbuf", "-oL", "./main"], stdout=PIPE, bufsize=1)
for line in iter(process.stdout.readline, b''):
    print line,
process.communicate() # close process' stream, wait for it to exit

Es sind auch andere Dienstprogramme verfügbar, siehe Deaktivieren Sie die Pufferung in der Pipe.

Oder verwenden Sie Pseudo-TTY

Um den Unterprozess glauben zu machen, dass er interaktiv läuft, könnten Sie verwenden pexpect Modul oder seine Entsprechungen für Codebeispiele, die verwenden pexpect und pty Module finden Sie unter Python-Unterprozess readlines() hängt. Hier ist eine Variation der pty dort bereitgestelltes Beispiel (es sollte unter Linux funktionieren):

#!/usr/bin/env python
import os
import pty
import sys
from select import select
from subprocess import Popen, STDOUT

master_fd, slave_fd = pty.openpty()  # provide tty to enable line buffering
process = Popen("./main", stdin=slave_fd, stdout=slave_fd, stderr=STDOUT,
                bufsize=0, close_fds=True)
timeout = .1 # ugly but otherwise `select` blocks on process' exit
# code is similar to _copy() from pty.py
with os.fdopen(master_fd, 'r+b', 0) as master:
    input_fds = [master, sys.stdin]
    while True:
        fds = select(input_fds, [], [], timeout)[0]
        if master in fds: # subprocess' output is ready
            data = os.read(master_fd, 512) # <-- doesn't block, may return less
            if not data: # EOF
                input_fds.remove(master)
            else:
                os.write(sys.stdout.fileno(), data) # copy to our stdout
        if sys.stdin in fds: # got user input
            data = os.read(sys.stdin.fileno(), 512)
            if not data:
                input_fds.remove(sys.stdin)
            else:
                master.write(data) # copy it to subprocess' stdin
        if not fds: # timeout in select()
            if process.poll() is not None: # subprocess ended
                # and no output is buffered <-- timeout + dead subprocess
                assert not select([master], [], [], 0)[0] # race is possible
                os.close(slave_fd) # subproces don't need it anymore
                break
rc = process.wait()
print("subprocess exited with status %d" % rc)

Oder verwenden pty über pexpect

pexpect wickelt pty Handhabung in Schnittstelle auf höherer Ebene:

#!/usr/bin/env python
import pexpect

child = pexpect.spawn("/.main")
for line in child:
    print line,
child.close()

F: Warum nicht einfach eine Pipe (popen()) verwenden? erklärt, warum Pseudo-TTY nützlich ist.

  • Ich denke, diese Antwort sollte als richtig gekennzeichnet werden, da sie am vollständigsten und prägnantesten ist und eine Vielzahl möglicher Lösungen und weiterführender Lektüren bietet. (Es ist eine großartige Antwort.) Aber ich denke wirklich, damit die Leser diese dichten Informationen verarbeiten können, braucht es unbedingt eine Art Überschrift zwischen den verschiedenen Ansätzen. Ich denke, die meisten Leute sehen den großen Python-Block und bekommen Angst, bevor sie erkennen, dass es nur eine von vielen möglichen Lösungen ist.

    – Scheintod

    28. Dezember 2013 um 10:01 Uhr

  • @Scheintod: Ich habe Abschnittsüberschriften hinzugefügt. Ich bin ehrlich: Ich habe diese Frage beantwortet, weil ich mitspielen wollte pty, select. Übrigens können Sie die Antwort selbst bearbeiten, wenn sie dadurch nicht merklich schlechter wird.

    – jfs

    28. Dezember 2013 um 14:41 Uhr

  • Kennen Sie ähnliche Lösungen für die Ausgabepufferung unter Windows?

    – kuchi

    12. Dezember 2017 um 16:10 Uhr

  • @kuchi Ich weiß es nicht

    – jfs

    12. Dezember 2017 um 20:17 Uhr

  • @kuchi schau mal pywinpty. Ich weiß nicht, ob das Ihre Frage beantwortet oder nicht, aber es scheint so etwas wie pty zu tun, aber für Windows.

    – Flötenfreak7

    7. März 2019 um 8:03 Uhr

Ihr Programm hängt nicht, es läuft nur sehr langsam. Ihr Programm verwendet gepufferte Ausgabe; das "2000\n" Daten werden nicht sofort nach stdout geschrieben, werden es aber irgendwann schaffen. In Ihrem Fall kann es dauern BUFSIZ/strlen("2000\n") Sekunden (wahrscheinlich 1638 Sekunden) bis zum Abschluss.

Nach dieser Zeile:

printf("2000\n");

hinzufügen

fflush(stdout);

  • Vielen Dank! Genau darum ging es

    – theo-braun

    10. Dezember 2013 um 20:18 Uhr

  • Danke Rob, wollte das gerade hinzufügen, damit er es schneller sehen kann. @ theoB610, beachten Sie, dass Sie immer noch den Zeilenumbruch benötigen.

    – codnodder

    10. Dezember 2013 um 20:25 Uhr

  • Sehr richtig! Danke @codnodder

    – theo-braun

    10. Dezember 2013 um 20:30 Uhr

Sehen Readline-Dokumente.

Dein Code:

process.stdout.readline

Wartet auf EOF oder einen Zeilenumbruch.

Ich kann nicht sagen, was Sie letztendlich versuchen, aber Ihrem printf einen Zeilenumbruch hinzuzufügen, z. printf("2000\n");sollte Ihnen zumindest den Einstieg erleichtern.

1251630cookie-checkUnterprozess des Python-C-Programms hängt bei „for line in iter“

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy