Lettura non bloccante su un sottoprocesso.PIPE in python


507

Sto usando il modulo sottoprocesso per avviare un sottoprocesso e connettermi al suo flusso di output (stdout). Voglio essere in grado di eseguire letture non bloccanti sul suo stdout. C'è un modo per rendere .readline non bloccante o per verificare se ci sono dati sullo stream prima che io invochi .readline? Vorrei che questo fosse portatile o almeno funzionasse su Windows e Linux.

ecco come lo faccio per ora (si sta bloccando .readlinese non sono disponibili dati):

p = subprocess.Popen('myprogram.exe', stdout = subprocess.PIPE)
output_str = p.stdout.readline()

14
(Proveniente da Google?) Tutti i PIPE si bloccheranno quando uno dei buffer dei PIPE viene riempito e non letto. ad es. deadlock stdout quando stderr è pieno. Non passare mai un TUBO che non si intende leggere.
Nasser Al-Wohaibi,

@ NasserAl-Wohaibi significa che è meglio creare sempre file allora?
Charlie Parker,

qualcosa che sono stato curioso di capire è il motivo per cui il suo blocco in primo luogo ... Sto chiedendo perché ho visto il commento:To avoid deadlocks: careful to: add \n to output, flush output, use readline() rather than read()
Charlie Parker

È "in base alla progettazione", in attesa di ricevere input.
Mathieu Pagé

Risposte:


403

fcntl, select, asyncprocNon aiuterà in questo caso.

Un modo affidabile per leggere un flusso senza bloccare indipendentemente dal sistema operativo è utilizzare Queue.get_nowait():

import sys
from subprocess import PIPE, Popen
from threading  import Thread

try:
    from queue import Queue, Empty
except ImportError:
    from Queue import Queue, Empty  # python 2.x

ON_POSIX = 'posix' in sys.builtin_module_names

def enqueue_output(out, queue):
    for line in iter(out.readline, b''):
        queue.put(line)
    out.close()

p = Popen(['myprogram.exe'], stdout=PIPE, bufsize=1, close_fds=ON_POSIX)
q = Queue()
t = Thread(target=enqueue_output, args=(p.stdout, q))
t.daemon = True # thread dies with the program
t.start()

# ... do other things here

# read line without blocking
try:  line = q.get_nowait() # or q.get(timeout=.1)
except Empty:
    print('no output yet')
else: # got line
    # ... do something with line

6
Sì, questo funziona per me, ho rimosso molto però. Include buone pratiche ma non sempre necessarie. Python 3.x 2.X compat e close_fds possono essere omessi, funzionerà comunque. Ma fai attenzione a ciò che fa tutto e non copiarlo alla cieca, anche se funziona! (In realtà la soluzione più semplice è usare un thread e fare una readline come ha fatto Seb, Qeues è solo un modo semplice per ottenere i dati, ce ne sono altri, i thread sono la risposta!)
Aki,

3
All'interno del thread, la chiamata per out.readlinebloccare il thread e il thread principale, e devo aspettare fino a quando readline ritorna prima che tutto il resto continui. Un modo semplice per aggirare questo? (Sto leggendo più righe dal mio processo, che è anche un altro file .py che esegue DB e cose simili)
Justin

3
@Justin: 'out.readline' non blocca il thread principale, ma viene eseguito in un altro thread.
jfs,

4
cosa succede se non riesco a chiudere il sottoprocesso, ad es. a causa di eccezioni? il thread del lettore stdout non morirà e python si bloccherà, anche se il thread principale è uscito, no? come si può aggirare questo? python 2.x non supporta l'uccisione dei thread, peggio ancora, non supporta l'interruzione. :( (ovviamente si dovrebbero gestire le eccezioni per assicurarsi che il sottoprocesso venga chiuso, ma nel caso in cui non lo fosse, cosa si può fare?)
n611x007

3
Ho creato alcuni wrapper amichevoli di questo nel pacchetto shelljob pypi.python.org/pypi/shelljob
edA-qa mort-ora-y

77

Ho avuto spesso un problema simile; I programmi Python che scrivo spesso devono avere la capacità di eseguire alcune funzionalità primarie, accettando contemporaneamente l'input dell'utente dalla riga di comando (stdin). Mettere semplicemente la funzionalità di gestione dell'input dell'utente in un altro thread non risolve il problema perché si readline()blocca e non ha timeout. Se la funzionalità principale è completa e non è più necessario attendere ulteriori input da parte dell'utente, in genere desidero uscire dal mio programma, ma non è possibile perché si readline()sta ancora bloccando nell'altro thread in attesa di una riga. Una soluzione che ho riscontrato a questo problema è rendere stdin un file non bloccante usando il modulo fcntl:

import fcntl
import os
import sys

# make stdin a non-blocking file
fd = sys.stdin.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

# user input handling thread
while mainThreadIsRunning:
      try: input = sys.stdin.readline()
      except: continue
      handleInput(input)

A mio avviso, questo è un po 'più pulito rispetto all'utilizzo dei moduli select o signal per risolvere questo problema, ma poi funziona solo su UNIX ...


1
Secondo i documenti, fcntl () può ricevere un descrittore di file o un oggetto che ha il metodo .fileno ().
Denilson Sá Maia,

10
La risposta di Jesse non è corretta. Secondo Guido, readline non funziona correttamente con la modalità non bloccante e non lo farà prima di Python 3000. bugs.python.org/issue1175#msg56041 Se si desidera utilizzare fcntl per impostare il file in modalità non bloccante, devi usare os.read () di livello inferiore e separare tu stesso le linee. Mischiare fcntl con chiamate di alto livello che eseguono il buffering di linea richiede problemi.
anonnn,

2
L'uso di readline sembra non corretta nella risposta di Python 2. Vedere anonnn stackoverflow.com/questions/375427/...
Catalin Iacob

10
Per favore, non usare loop occupati. Utilizzare poll () con un timeout per attendere i dati.
Ivo Danihelka,

@Stefano come viene buffer_sizedefinito?
gatto

39

Python 3.4 introduce una nuova API provvisoria per il asynciomodulo IO asincrono .

L'approccio è simile alla twistedrisposta basata su @Bryan Ward : definire un protocollo e i suoi metodi vengono chiamati non appena i dati sono pronti:

#!/usr/bin/env python3
import asyncio
import os

class SubprocessProtocol(asyncio.SubprocessProtocol):
    def pipe_data_received(self, fd, data):
        if fd == 1: # got stdout data (bytes)
            print(data)

    def connection_lost(self, exc):
        loop.stop() # end loop.run_forever()

if os.name == 'nt':
    loop = asyncio.ProactorEventLoop() # for subprocess' pipes on Windows
    asyncio.set_event_loop(loop)
else:
    loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(loop.subprocess_exec(SubprocessProtocol, 
        "myprogram.exe", "arg1", "arg2"))
    loop.run_forever()
finally:
    loop.close()

Vedi "Sottoprocesso" nei documenti .

Esiste un'interfaccia di alto livello asyncio.create_subprocess_exec()che restituisce Processoggetti che consente di leggere una riga in modo asincrono usando StreamReader.readline()coroutine (con sintassi async/ awaitPython 3.5+ ):

#!/usr/bin/env python3.5
import asyncio
import locale
import sys
from asyncio.subprocess import PIPE
from contextlib import closing

async def readline_and_kill(*args):
    # start child process
    process = await asyncio.create_subprocess_exec(*args, stdout=PIPE)

    # read line (sequence of bytes ending with b'\n') asynchronously
    async for line in process.stdout:
        print("got line:", line.decode(locale.getpreferredencoding(False)))
        break
    process.kill()
    return await process.wait() # wait for the child process to exit


if sys.platform == "win32":
    loop = asyncio.ProactorEventLoop()
    asyncio.set_event_loop(loop)
else:
    loop = asyncio.get_event_loop()

with closing(loop):
    sys.exit(loop.run_until_complete(readline_and_kill(
        "myprogram.exe", "arg1", "arg2")))

readline_and_kill() svolge le seguenti attività:

  • avvia il sottoprocesso, reindirizza il suo stdout su una pipe
  • legge una riga dallo stdout del sottoprocesso in modo asincrono
  • uccidi sottoprocesso
  • aspetta che esca

Ogni passaggio potrebbe essere limitato da secondi di timeout, se necessario.


Quando provo qualcosa del genere usando le coroutine di Python 3.4, ottengo l'output solo una volta eseguito l'intero script. Vorrei vedere una riga di output stampata, non appena il sottoprocesso stampa una riga. Ecco cosa ho: pastebin.com/qPssFGep .
flutefreak7,

1
@ flutefreak7: i problemi di buffering non sono correlati alla domanda corrente. Segui il link per possibili soluzioni.
jfs

Grazie! Risolto il problema per la mia sceneggiatura semplicemente usando in print(text, flush=True)modo che il testo stampato fosse immediatamente disponibile per la chiamata dell'osservatore readline. Quando l'ho provato con l'eseguibile basato su Fortran, in realtà voglio avvolgere / guardare, non bufferizza il suo output, quindi si comporta come previsto.
flutefreak7,

È possibile consentire al processo secondario di persistere ed eseguire ulteriori operazioni di lettura / scrittura. readline_and_kill, nel tuo secondo script, funziona in modo molto simile subprocess.comunicateal fatto che termina il processo dopo un'operazione di lettura / scrittura. Vedo anche che stai utilizzando una singola pipe stdout, che i sottoprocessi gestiscono come non bloccante. Sto provando a usare entrambi stdoute stderr trovo che finisca per bloccare .
Carel,

@Carel il codice nella risposta funziona come previsto esplicitamente nella risposta. È possibile implementare altri comportamenti se lo si desidera. Entrambe le pipe sono ugualmente non bloccanti se utilizzate, ecco un esempio su come leggere contemporaneamente da entrambe le pipe .
jfs,

19

Prova il modulo asyncproc . Per esempio:

import os
from asyncproc import Process
myProc = Process("myprogram.app")

while True:
    # check to see if process has ended
    poll = myProc.wait(os.WNOHANG)
    if poll != None:
        break
    # print any new output
    out = myProc.read()
    if out != "":
        print out

Il modulo si occupa di tutto il threading come suggerito da S.Lott.


1
Assolutamente brillante. Molto più semplice del modulo di sottoprocesso non elaborato. Funziona perfettamente per me su Ubuntu.
Cerin,

12
asyncproc non funziona su Windows e Windows non supporta os.WNOHANG :-(
Bryan Oakley

26
asyncproc è GPL, che ne limita ulteriormente l'utilizzo :-(
Bryan Oakley,

Grazie. Una piccola cosa: sembra che sostituire le schede con 8 spazi in asyncproc.py sia la strada da percorrere :)
benjaoming,

Non sembra che sia possibile ottenere il codice di ritorno del processo avviato tramite il modulo asyncproc; solo l'output che ha generato.
grayaii,

17

Puoi farlo facilmente in Twisted . A seconda della tua base di codice esistente, potrebbe non essere così facile da usare, ma se stai costruendo un'applicazione contorta, cose come questa diventano quasi banali. Si crea una ProcessProtocolclasse e si sovrascrive il outReceived()metodo. Twisted (a seconda del reattore utilizzato) di solito è solo un grande select()ciclo con callback installati per gestire i dati da diversi descrittori di file (spesso socket di rete). Quindi il outReceived()metodo consiste semplicemente nell'installare un callback per la gestione dei dati provenienti STDOUT. Un semplice esempio che dimostra questo comportamento è il seguente:

from twisted.internet import protocol, reactor

class MyProcessProtocol(protocol.ProcessProtocol):

    def outReceived(self, data):
        print data

proc = MyProcessProtocol()
reactor.spawnProcess(proc, './myprogram', ['./myprogram', 'arg1', 'arg2', 'arg3'])
reactor.run()

La documentazione di Twisted contiene alcune buone informazioni al riguardo.

Se costruisci l'intera applicazione attorno a Twisted, rende la comunicazione asincrona con altri processi, locali o remoti, davvero elegante come questa. D'altra parte, se il tuo programma non è basato su Twisted, questo non sarà davvero utile. Speriamo che questo possa essere utile per altri lettori, anche se non è applicabile per la tua particolare applicazione.


non buono. selectnon dovrebbe funzionare su Windows con descrittori di file, secondo i documenti
n611x007

2
@naxa Non credo select()che si riferisca a quello che sei tu. Sto assumendo questo perché Twistedfunziona su Windows ...
notbad.jpeg


1
"Twisted (a seconda del reattore utilizzato) di solito è solo un grande ciclo select ()" significa che ci sono diversi reattori tra cui scegliere. L' select()uno è il massimo che si portatile su sistemi Unix e Unix-simili, ma ci sono anche due reattori disponibile per Windows: twistedmatrix.com/documents/current/core/howto/...
clacke

14

Utilizzare select & read (1).

import subprocess     #no new requirements
def readAllSoFar(proc, retVal=''): 
  while (select.select([proc.stdout],[],[],0)[0]!=[]):   
    retVal+=proc.stdout.read(1)
  return retVal
p = subprocess.Popen(['/bin/ls'], stdout=subprocess.PIPE)
while not p.poll():
  print (readAllSoFar(p))

Per readline () - come:

lines = ['']
while not p.poll():
  lines = readAllSoFar(p, lines[-1]).split('\n')
  for a in range(len(lines)-1):
    print a
lines = readAllSoFar(p, lines[-1]).split('\n')
for a in range(len(lines)-1):
  print a

6
non buono. selectnon dovrebbe funzionare su Windows con descrittori di file, secondo i documenti
n611x007

OH MIO DIO. Leggi megabyte, o forse gigabyte un carattere alla volta ... questa è la peggior idea che ho visto da molto tempo ... inutile menzionare, questo codice non funziona, perché proc.stdout.read()non importa quanto piccolo sia l'argomento una chiamata di blocco.
wvxvw,

OSError: [WinError 10093] Either the application has not called WSAStartup, or WSAStartup failed
nmz787,

8

Una soluzione è creare un altro processo per eseguire la lettura del processo o creare un thread del processo con un timeout.

Ecco la versione filettata di una funzione di timeout:

http://code.activestate.com/recipes/473878/

Tuttavia, devi leggere lo stdout mentre sta arrivando? Un'altra soluzione potrebbe essere quella di scaricare l'output in un file e attendere il completamento del processo utilizzando p.wait () .

f = open('myprogram_output.txt','w')
p = subprocess.Popen('myprogram.exe', stdout=f)
p.wait()
f.close()


str = open('myprogram_output.txt','r').read()

sembra che il thread di recpie non uscirebbe dopo il timeout e ucciderlo dipende dalla possibilità di uccidere il sottoprocesso (sg. altrimenti non correlato a questo proposito) che legge (una cosa che dovresti essere in grado di fare, ma nel caso in cui non puoi ..) .
n611x007

7

Disclaimer: funziona solo per il tornado

Puoi farlo impostando fd in modo che non sia bloccato e quindi usa ioloop per registrare i callback. L'ho impacchettato in un uovo chiamato tornado_subprocess e puoi installarlo tramite PyPI:

easy_install tornado_subprocess

ora puoi fare qualcosa del genere:

import tornado_subprocess
import tornado.ioloop

    def print_res( status, stdout, stderr ) :
    print status, stdout, stderr
    if status == 0:
        print "OK:"
        print stdout
    else:
        print "ERROR:"
        print stderr

t = tornado_subprocess.Subprocess( print_res, timeout=30, args=[ "cat", "/etc/passwd" ] )
t.start()
tornado.ioloop.IOLoop.instance().start()

puoi anche usarlo con un RequestHandler

class MyHandler(tornado.web.RequestHandler):
    def on_done(self, status, stdout, stderr):
        self.write( stdout )
        self.finish()

    @tornado.web.asynchronous
    def get(self):
        t = tornado_subprocess.Subprocess( self.on_done, timeout=30, args=[ "cat", "/etc/passwd" ] )
        t.start()

Grazie per la bella funzionalità! Giusto per chiarire, perché non possiamo semplicemente utilizzare threading.Threadper creare nuovi processi non bloccanti? L'ho usato on_messagenell'istanza di websocket Tornado e ha fatto bene il lavoro.
VisioN,

1
il threading è per lo più scoraggiato nel tornado. vanno bene per le piccole funzioni di breve durata. Si può leggere su qui: stackoverflow.com/questions/7846323/tornado-web-and-threads github.com/facebook/tornado/wiki/Threading-and-concurrency
Vukasin toRoman

@VukasinToroman mi hai davvero salvato qui con questo. grazie mille per il modulo tornado_subprocess :)
James Gentes,

funziona su Windows? (nota che select, con i descrittori di file, non lo fa )
n611x007

Questa libreria non utilizza la selectchiamata. Non ho provato questo in Windows, ma probabilmente potresti avere problemi poiché la lib sta usando il fcntlmodulo. Quindi in breve: no, questo probabilmente non funzionerà con Windows.
Vukasin Toroman,

6

Le soluzioni esistenti non hanno funzionato per me (dettagli sotto). Ciò che alla fine ha funzionato è stato implementare readline usando read (1) (basato su questa risposta ). Quest'ultimo non blocca:

from subprocess import Popen, PIPE
from threading import Thread
def process_output(myprocess): #output-consuming thread
    nextline = None
    buf = ''
    while True:
        #--- extract line using read(1)
        out = myprocess.stdout.read(1)
        if out == '' and myprocess.poll() != None: break
        if out != '':
            buf += out
            if out == '\n':
                nextline = buf
                buf = ''
        if not nextline: continue
        line = nextline
        nextline = None

        #--- do whatever you want with line here
        print 'Line is:', line
    myprocess.stdout.close()

myprocess = Popen('myprogram.exe', stdout=PIPE) #output-producing process
p1 = Thread(target=process_output, args=(dcmpid,)) #output-consuming thread
p1.daemon = True
p1.start()

#--- do whatever here and then kill process and thread if needed
if myprocess.poll() == None: #kill process; will automatically stop thread
    myprocess.kill()
    myprocess.wait()
if p1 and p1.is_alive(): #wait for thread to finish
    p1.join()

Perché le soluzioni esistenti non hanno funzionato:

  1. Le soluzioni che richiedono readline (comprese quelle basate sulla coda) si bloccano sempre. È difficile (impossibile?) Uccidere il thread che esegue readline. Viene ucciso solo al termine del processo che lo ha creato, ma non quando viene interrotto il processo di produzione dell'output.
  2. La miscelazione di fcntl di basso livello con chiamate readline di alto livello potrebbe non funzionare correttamente come ha sottolineato anonnn.
  3. L'uso di select.poll () è pulito, ma non funziona su Windows secondo i documenti di Python.
  4. L'uso di librerie di terze parti sembra eccessivo per questa attività e aggiunge ulteriori dipendenze.

1
1. q.get_nowait()dalla mia risposta non devo mai bloccare, questo è il punto di usarlo. 2. Il thread che esegue readline ( enqueue_output()funzione ) termina ad es. Su EOF, incluso il caso in cui il processo di produzione dell'output viene interrotto. Se credi che non sia così; per favore, fornisci un esempio di codice minimo completo che mostra diversamente (forse come una nuova domanda ).
jfs

1
@sebastian Ho trascorso un'ora o più cercando di trovare un esempio minimo. Alla fine, devo ammettere che la tua risposta gestisce tutti i casi. Immagino che non abbia funzionato prima per me perché quando stavo cercando di uccidere il processo di produzione dell'output, era già stato ucciso e ha dato un errore di debug difficile. L'ora è stata ben spesa, perché, pur presentando un esempio minimo, ho potuto trovare una soluzione più semplice.
Vikram Pudi,

Potresti pubblicare anche la soluzione più semplice? :) (se diverso da quello di Sebastian)
n611x007

@ danger89: penso dcmpid = myprocess.
ViFI,

In condizione dopo aver letto read () (subito dopo mentre True): out non sarà mai stringa vuota perché leggi almeno string / byte con lunghezza di 1.
sergzach

6

Ecco il mio codice, usato per catturare ogni output dal sottoprocesso al più presto, comprese le linee parziali. Pompa contemporaneamente e stdout e stderr in ordine quasi corretto.

Testato e funzionato correttamente su Python 2.7 Linux e Windows.

#!/usr/bin/python
#
# Runner with stdout/stderr catcher
#
from sys import argv
from subprocess import Popen, PIPE
import os, io
from threading import Thread
import Queue
def __main__():
    if (len(argv) > 1) and (argv[-1] == "-sub-"):
        import time, sys
        print "Application runned!"
        time.sleep(2)
        print "Slept 2 second"
        time.sleep(1)
        print "Slept 1 additional second",
        time.sleep(2)
        sys.stderr.write("Stderr output after 5 seconds")
        print "Eol on stdin"
        sys.stderr.write("Eol on stderr\n")
        time.sleep(1)
        print "Wow, we have end of work!",
    else:
        os.environ["PYTHONUNBUFFERED"]="1"
        try:
            p = Popen( argv + ["-sub-"],
                       bufsize=0, # line-buffered
                       stdin=PIPE, stdout=PIPE, stderr=PIPE )
        except WindowsError, W:
            if W.winerror==193:
                p = Popen( argv + ["-sub-"],
                           shell=True, # Try to run via shell
                           bufsize=0, # line-buffered
                           stdin=PIPE, stdout=PIPE, stderr=PIPE )
            else:
                raise
        inp = Queue.Queue()
        sout = io.open(p.stdout.fileno(), 'rb', closefd=False)
        serr = io.open(p.stderr.fileno(), 'rb', closefd=False)
        def Pump(stream, category):
            queue = Queue.Queue()
            def rdr():
                while True:
                    buf = stream.read1(8192)
                    if len(buf)>0:
                        queue.put( buf )
                    else:
                        queue.put( None )
                        return
            def clct():
                active = True
                while active:
                    r = queue.get()
                    try:
                        while True:
                            r1 = queue.get(timeout=0.005)
                            if r1 is None:
                                active = False
                                break
                            else:
                                r += r1
                    except Queue.Empty:
                        pass
                    inp.put( (category, r) )
            for tgt in [rdr, clct]:
                th = Thread(target=tgt)
                th.setDaemon(True)
                th.start()
        Pump(sout, 'stdout')
        Pump(serr, 'stderr')

        while p.poll() is None:
            # App still working
            try:
                chan,line = inp.get(timeout = 1.0)
                if chan=='stdout':
                    print "STDOUT>>", line, "<?<"
                elif chan=='stderr':
                    print " ERROR==", line, "=?="
            except Queue.Empty:
                pass
        print "Finish"

if __name__ == '__main__':
    __main__()

Una delle poche risposte che ti permettono di leggere cose che non finiscono necessariamente con una nuova riga.
totaam

5

Aggiungo questo problema per leggere alcuni sottoprocessi. Apri stdout. Ecco la mia soluzione di lettura non bloccante:

import fcntl

def non_block_read(output):
    fd = output.fileno()
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
    try:
        return output.read()
    except:
        return ""

# Use example
from subprocess import *
sb = Popen("echo test && sleep 1000", shell=True, stdout=PIPE)
sb.kill()

# sb.stdout.read() # <-- This will block
non_block_read(sb.stdout)
'test\n'

5
fcntl non funziona su Windows, secondo i documenti .
n611x007

@anatolytechtonik usa msvcrt.kbhit()invece
cat

4

Questa versione di lettura non bloccante non richiede moduli speciali e funzionerà immediatamente sulla maggior parte delle distribuzioni Linux.

import os
import sys
import time
import fcntl
import subprocess

def async_read(fd):
    # set non-blocking flag while preserving old flags
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
    # read char until EOF hit
    while True:
        try:
            ch = os.read(fd.fileno(), 1)
            # EOF
            if not ch: break                                                                                                                                                              
            sys.stdout.write(ch)
        except OSError:
            # waiting for data be available on fd
            pass

def shell(args, async=True):
    # merge stderr and stdout
    proc = subprocess.Popen(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    if async: async_read(proc.stdout)
    sout, serr = proc.communicate()
    return (sout, serr)

if __name__ == '__main__':
    cmd = 'ping 8.8.8.8'
    sout, serr = shell(cmd.split())

3

Ecco una semplice soluzione basata su thread che:

  • funziona su Linux e Windows (non basandosi su select ).
  • legge sia stdoutestderr modo asincrono.
  • non si basa sul polling attivo con tempo di attesa arbitrario (CPU friendly).
  • non utilizza asyncio(che potrebbe essere in conflitto con altre librerie).
  • viene eseguito fino al termine del processo figlio.

printer.py

import time
import sys

sys.stdout.write("Hello\n")
sys.stdout.flush()
time.sleep(1)
sys.stdout.write("World!\n")
sys.stdout.flush()
time.sleep(1)
sys.stderr.write("That's an error\n")
sys.stderr.flush()
time.sleep(2)
sys.stdout.write("Actually, I'm fine\n")
sys.stdout.flush()
time.sleep(1)

reader.py

import queue
import subprocess
import sys
import threading


def enqueue_stream(stream, queue, type):
    for line in iter(stream.readline, b''):
        queue.put(str(type) + line.decode('utf-8'))
    stream.close()


def enqueue_process(process, queue):
    process.wait()
    queue.put('x')


p = subprocess.Popen('python printer.py', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
q = queue.Queue()
to = threading.Thread(target=enqueue_stream, args=(p.stdout, q, 1))
te = threading.Thread(target=enqueue_stream, args=(p.stderr, q, 2))
tp = threading.Thread(target=enqueue_process, args=(p, q))
te.start()
to.start()
tp.start()

while True:
    line = q.get()
    if line[0] == 'x':
        break
    if line[0] == '2':  # stderr
        sys.stdout.write("\033[0;31m")  # ANSI red color
    sys.stdout.write(line[1:])
    if line[0] == '2':
        sys.stdout.write("\033[0m")  # reset ANSI code
    sys.stdout.flush()

tp.join()
to.join()
te.join()

2

Aggiungendo questa risposta qui poiché offre la possibilità di impostare pipe non bloccanti su Windows e Unix.

Tutti i ctypesdettagli sono grazie alla risposta di @ techtonik .

Esiste una versione leggermente modificata da utilizzare sia su sistemi Unix che Windows.

  • Compatibile con Python3 (sono necessarie solo piccole modifiche) .
  • Include la versione posix e definisce l'eccezione da utilizzare per entrambi.

In questo modo è possibile utilizzare la stessa funzione ed eccezione per il codice Unix e Windows.

# pipe_non_blocking.py (module)
"""
Example use:

    p = subprocess.Popen(
            command,
            stdout=subprocess.PIPE,
            )

    pipe_non_blocking_set(p.stdout.fileno())

    try:
        data = os.read(p.stdout.fileno(), 1)
    except PortableBlockingIOError as ex:
        if not pipe_non_blocking_is_error_blocking(ex):
            raise ex
"""


__all__ = (
    "pipe_non_blocking_set",
    "pipe_non_blocking_is_error_blocking",
    "PortableBlockingIOError",
    )

import os


if os.name == "nt":
    def pipe_non_blocking_set(fd):
        # Constant could define globally but avoid polluting the name-space
        # thanks to: /programming/34504970
        import msvcrt

        from ctypes import windll, byref, wintypes, WinError, POINTER
        from ctypes.wintypes import HANDLE, DWORD, BOOL

        LPDWORD = POINTER(DWORD)

        PIPE_NOWAIT = wintypes.DWORD(0x00000001)

        def pipe_no_wait(pipefd):
            SetNamedPipeHandleState = windll.kernel32.SetNamedPipeHandleState
            SetNamedPipeHandleState.argtypes = [HANDLE, LPDWORD, LPDWORD, LPDWORD]
            SetNamedPipeHandleState.restype = BOOL

            h = msvcrt.get_osfhandle(pipefd)

            res = windll.kernel32.SetNamedPipeHandleState(h, byref(PIPE_NOWAIT), None, None)
            if res == 0:
                print(WinError())
                return False
            return True

        return pipe_no_wait(fd)

    def pipe_non_blocking_is_error_blocking(ex):
        if not isinstance(ex, PortableBlockingIOError):
            return False
        from ctypes import GetLastError
        ERROR_NO_DATA = 232

        return (GetLastError() == ERROR_NO_DATA)

    PortableBlockingIOError = OSError
else:
    def pipe_non_blocking_set(fd):
        import fcntl
        fl = fcntl.fcntl(fd, fcntl.F_GETFL)
        fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
        return True

    def pipe_non_blocking_is_error_blocking(ex):
        if not isinstance(ex, PortableBlockingIOError):
            return False
        return True

    PortableBlockingIOError = BlockingIOError

Per evitare la lettura di dati incompleti, ho finito per scrivere il mio generatore readline (che restituisce la stringa di byte per ogni riga).

È un generatore, quindi puoi ad esempio ...

def non_blocking_readlines(f, chunk=1024):
    """
    Iterate over lines, yielding b'' when nothings left
    or when new data is not yet available.

    stdout_iter = iter(non_blocking_readlines(process.stdout))

    line = next(stdout_iter)  # will be a line or b''.
    """
    import os

    from .pipe_non_blocking import (
            pipe_non_blocking_set,
            pipe_non_blocking_is_error_blocking,
            PortableBlockingIOError,
            )

    fd = f.fileno()
    pipe_non_blocking_set(fd)

    blocks = []

    while True:
        try:
            data = os.read(fd, chunk)
            if not data:
                # case were reading finishes with no trailing newline
                yield b''.join(blocks)
                blocks.clear()
        except PortableBlockingIOError as ex:
            if not pipe_non_blocking_is_error_blocking(ex):
                raise ex

            yield b''
            continue

        while True:
            n = data.find(b'\n')
            if n == -1:
                break

            yield b''.join(blocks) + data[:n + 1]
            data = data[n + 1:]
            blocks.clear()
        blocks.append(data)

(1) questo commento indica che readline()non funziona con pipe non bloccanti (come impostate usando fcntl) su Python 2 - pensi che non sia più corretto? (la mia risposta contiene il link ( fcntl) che fornisce le stesse informazioni ma ora sembra eliminato). (2) Guarda come multiprocessing.connection.PipeutilizzaSetNamedPipeHandleState
jfs

L'ho provato solo su Python3. Ma ho visto anche queste informazioni e mi aspetto che rimangano valide. Ho anche scritto il mio codice da utilizzare al posto di readline, ho aggiornato la mia risposta per includerlo.
ideasman42

2

Ho il problema con l'interrogatore originale, ma non volevo invocare discussioni. Ho mescolato la soluzione di Jesse con un read () diretto dalla pipe, e il mio buffer-handler per le letture di riga (tuttavia, il mio processo secondario - ping - ha sempre scritto righe complete <una dimensione di pagina del sistema). Evito l'attesa indaffarata leggendo solo in un orologio io registrato su un oggetto. In questi giorni di solito eseguo il codice all'interno di un oggetto MainLoop per evitare discussioni.

def set_up_ping(ip, w):
# run the sub-process
# watch the resultant pipe
p = subprocess.Popen(['/bin/ping', ip], stdout=subprocess.PIPE)
# make stdout a non-blocking file
fl = fcntl.fcntl(p.stdout, fcntl.F_GETFL)
fcntl.fcntl(p.stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)
stdout_gid = gobject.io_add_watch(p.stdout, gobject.IO_IN, w)
return stdout_gid # for shutting down

L'osservatore è

def watch(f, *other):
print 'reading',f.read()
return True

E il programma principale imposta un ping e quindi chiama gobject mail loop.

def main():
set_up_ping('192.168.1.8', watch)
# discard gid as unused here
gobject.MainLoop().run()

Qualsiasi altro lavoro è associato ai callback nel gobject.


2

Le cose vanno molto meglio nel moderno Python.

Ecco un semplice programma figlio, "hello.py":

#!/usr/bin/env python3

while True:
    i = input()
    if i == "quit":
        break
    print(f"hello {i}")

E un programma per interagire con esso:

import asyncio


async def main():
    proc = await asyncio.subprocess.create_subprocess_exec(
        "./hello.py", stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE
    )
    proc.stdin.write(b"bob\n")
    print(await proc.stdout.read(1024))
    proc.stdin.write(b"alice\n")
    print(await proc.stdout.read(1024))
    proc.stdin.write(b"quit\n")
    await proc.wait()


asyncio.run(main())

Che stampa:

b'hello bob\n'
b'hello alice\n'

Si noti che il modello reale, che è presente anche in quasi tutte le risposte precedenti, sia qui che nelle domande correlate, è di impostare il descrittore di file stdout del bambino su non-blocking e quindi eseguirne il polling in una sorta di ciclo di selezione. In questi giorni, ovviamente, quel ciclo è fornito da asyncio.


1

La selezione modulo ti aiuta a determinare dove si trova il prossimo input utile.

Tuttavia, sei quasi sempre più felice con thread separati. Uno fa un blocco leggendo lo stdin, un altro fa dovunque tu non voglia essere bloccato.


11
Penso che questa risposta non sia utile per due motivi: (a) Il modulo select non funzionerà su pipe in Windows (come chiaramente indicato dal link fornito), il che vanifica le intenzioni dell'OP di avere una soluzione portatile. (b) I thread asincroni non consentono un dialogo sincrono tra il processo genitore e figlio. Cosa succede se il processo genitore desidera inviare l'azione successiva in base alla riga successiva letta dal bambino ?!
ThomasH,

4
select non è inoltre utile in quanto le letture di Python si bloccheranno anche dopo la selezione, perché non ha una semantica C standard e non restituisce dati parziali.
Helmut Grohne,

Una tregua separata per la lettura dall'output di un bambino ha risolto il mio problema che era simile a questo. Se hai bisogno di interazioni sincrone suppongo che non puoi usare questa soluzione (a meno che tu non sappia quale output aspettarti). Avrei accettato questa risposta
Emiliano,

1

perché preoccuparsi di thread e code? a differenza di readline (), BufferedReader.read1 () non bloccherà in attesa di \ r \ n, restituisce APPENA POSSIBILE se c'è qualche output in arrivo.

#!/usr/bin/python
from subprocess import Popen, PIPE, STDOUT
import io

def __main__():
    try:
        p = Popen( ["ping", "-n", "3", "127.0.0.1"], stdin=PIPE, stdout=PIPE, stderr=STDOUT )
    except: print("Popen failed"); quit()
    sout = io.open(p.stdout.fileno(), 'rb', closefd=False)
    while True:
        buf = sout.read1(1024)
        if len(buf) == 0: break
        print buf,

if __name__ == '__main__':
    __main__()

Restituirà al più presto se non arriva nulla? In caso contrario, sta bloccando.
Mathieu Pagé

@ MathieuPagé ha ragione. read1bloccherà se i primi blocchi di lettura sottostanti, che si verificano quando la pipe è ancora aperta ma non è disponibile alcun input.
Jack O'Connor,

1

Nel mio caso avevo bisogno di un modulo di registrazione che cattura l'output dalle applicazioni in background e lo aumenta (aggiungendo timestamp, colori, ecc.).

Ho finito con un thread in background che esegue l'I / O effettivo. Il codice seguente è solo per piattaforme POSIX. Ho spogliato parti non essenziali.

Se qualcuno utilizzerà questa bestia per lunghi periodi, considera la gestione di descrittori aperti. Nel mio caso non è stato un grosso problema.

# -*- python -*-
import fcntl
import threading
import sys, os, errno
import subprocess

class Logger(threading.Thread):
    def __init__(self, *modules):
        threading.Thread.__init__(self)
        try:
            from select import epoll, EPOLLIN
            self.__poll = epoll()
            self.__evt = EPOLLIN
            self.__to = -1
        except:
            from select import poll, POLLIN
            print 'epoll is not available'
            self.__poll = poll()
            self.__evt = POLLIN
            self.__to = 100
        self.__fds = {}
        self.daemon = True
        self.start()

    def run(self):
        while True:
            events = self.__poll.poll(self.__to)
            for fd, ev in events:
                if (ev&self.__evt) != self.__evt:
                    continue
                try:
                    self.__fds[fd].run()
                except Exception, e:
                    print e

    def add(self, fd, log):
        assert not self.__fds.has_key(fd)
        self.__fds[fd] = log
        self.__poll.register(fd, self.__evt)

class log:
    logger = Logger()

    def __init__(self, name):
        self.__name = name
        self.__piped = False

    def fileno(self):
        if self.__piped:
            return self.write
        self.read, self.write = os.pipe()
        fl = fcntl.fcntl(self.read, fcntl.F_GETFL)
        fcntl.fcntl(self.read, fcntl.F_SETFL, fl | os.O_NONBLOCK)
        self.fdRead = os.fdopen(self.read)
        self.logger.add(self.read, self)
        self.__piped = True
        return self.write

    def __run(self, line):
        self.chat(line, nl=False)

    def run(self):
        while True:
            try: line = self.fdRead.readline()
            except IOError, exc:
                if exc.errno == errno.EAGAIN:
                    return
                raise
            self.__run(line)

    def chat(self, line, nl=True):
        if nl: nl = '\n'
        else: nl = ''
        sys.stdout.write('[%s] %s%s' % (self.__name, line, nl))

def system(command, param=[], cwd=None, env=None, input=None, output=None):
    args = [command] + param
    p = subprocess.Popen(args, cwd=cwd, stdout=output, stderr=output, stdin=input, env=env, bufsize=0)
    p.wait()

ls = log('ls')
ls.chat('go')
system("ls", ['-l', '/'], output=ls)

date = log('date')
date.chat('go')
system("date", output=date)

1

Il mio problema è un po 'diverso poiché volevo raccogliere sia stdout che stderr da un processo in esecuzione, ma alla fine lo stesso poiché volevo rendere l'output in un widget come generato.

Non volevo ricorrere a molte delle soluzioni alternative proposte usando Code o Thread aggiuntivi in ​​quanto non dovrebbero essere necessari per eseguire un'attività così comune come l'esecuzione di un altro script e la raccolta del suo output.

Dopo aver letto le soluzioni proposte e i documenti di Python ho risolto il mio problema con l'implementazione di seguito. Sì, funziona solo con POSIX mentre sto usando la selectchiamata di funzione.

Sono d'accordo che i documenti sono confusi e l'implementazione è imbarazzante per un compito di scripting così comune. Credo che le versioni precedenti di Python abbiano impostazioni predefinite Popendiverse e spiegazioni diverse in modo da creare molta confusione. Questo sembra funzionare bene sia per Python 2.7.12 che 3.5.2.

La chiave era impostare il bufsize=1buffering di linea e quindi universal_newlines=Trueelaborarlo come file di testo anziché come binario che sembra diventare il valore predefinito durante l'impostazione bufsize=1.

class workerThread(QThread):
   def __init__(self, cmd):
      QThread.__init__(self)
      self.cmd = cmd
      self.result = None           ## return code
      self.error = None            ## flag indicates an error
      self.errorstr = ""           ## info message about the error

   def __del__(self):
      self.wait()
      DEBUG("Thread removed")

   def run(self):
      cmd_list = self.cmd.split(" ")   
      try:
         cmd = subprocess.Popen(cmd_list, bufsize=1, stdin=None
                                        , universal_newlines=True
                                        , stderr=subprocess.PIPE
                                        , stdout=subprocess.PIPE)
      except OSError:
         self.error = 1
         self.errorstr = "Failed to execute " + self.cmd
         ERROR(self.errorstr)
      finally:
         VERBOSE("task started...")
      import select
      while True:
         try:
            r,w,x = select.select([cmd.stdout, cmd.stderr],[],[])
            if cmd.stderr in r:
               line = cmd.stderr.readline()
               if line != "":
                  line = line.strip()
                  self.emit(SIGNAL("update_error(QString)"), line)
            if cmd.stdout in r:
               line = cmd.stdout.readline()
               if line == "":
                  break
               line = line.strip()
               self.emit(SIGNAL("update_output(QString)"), line)
         except IOError:
            pass
      cmd.wait()
      self.result = cmd.returncode
      if self.result < 0:
         self.error = 1
         self.errorstr = "Task terminated by signal " + str(self.result)
         ERROR(self.errorstr)
         return
      if self.result:
         self.error = 1
         self.errorstr = "exit code " + str(self.result)
         ERROR(self.errorstr)
         return
      return

ERRORE, DEBUG e VERBOSE sono semplicemente macro che stampano l'output sul terminale.

Questa soluzione è efficace al 99,99% di IMHO in quanto utilizza ancora la readlinefunzione di blocco , quindi supponiamo che il processo secondario sia gradevole e produca linee complete.

Accolgo con favore il feedback per migliorare la soluzione poiché sono ancora nuovo in Python.


In questo caso particolare, puoi impostare stderr = subprocess.STDOUT nel costruttore Popen e ottenere tutto l'output da cmd.stdout.readline ().
Aaron,

Bel esempio chiaro. Ho avuto problemi con select.select () ma questo mi ha risolto.
Maharvey67,


0

Lavorando sulla risposta di JF Sebastian e su diverse altre fonti, ho messo insieme un semplice gestore di sottoprocessi. Fornisce la lettura non bloccante della richiesta, oltre a eseguire diversi processi in parallelo. Non utilizza alcuna chiamata specifica del sistema operativo (che sono a conoscenza) e quindi dovrebbe funzionare ovunque.

È disponibile da Pypi, quindi solo pip install shelljob. Fare riferimento alla pagina del progetto per esempi e documenti completi.


0

EDIT: questa implementazione si blocca ancora. Usa invece la risposta di JFSebastian .

Ho provato la risposta migliore , ma il rischio aggiuntivo e la manutenzione del codice thread erano preoccupanti.

Guardando attraverso il modulo io (ed essendo limitato a 2.6), ho trovato BufferedReader. Questa è la mia soluzione senza thread e non bloccante.

import io
from subprocess import PIPE, Popen

p = Popen(['myprogram.exe'], stdout=PIPE)

SLEEP_DELAY = 0.001

# Create an io.BufferedReader on the file descriptor for stdout
with io.open(p.stdout.fileno(), 'rb', closefd=False) as buffer:
  while p.poll() == None:
      time.sleep(SLEEP_DELAY)
      while '\n' in bufferedStdout.peek(bufferedStdout.buffer_size):
          line = buffer.readline()
          # do stuff with the line

  # Handle any remaining output after the process has ended
  while buffer.peek():
    line = buffer.readline()
    # do stuff with the line

hai provato for line in iter(p.stdout.readline, ""): # do stuff with the line? È senza thread (thread singolo) e si blocca quando il codice si blocca.
jfs

@ jf-sebastian Sì, alla fine sono tornato alla tua risposta. La mia implementazione è ancora occasionalmente bloccata. Modificherò la mia risposta per avvertire gli altri di non seguire questa strada.
rom

0

Recentemente mi sono imbattuto nello stesso problema che ho bisogno di leggere una riga alla volta dallo stream (tail run in subprocess) in modalità non bloccante Volevo evitare i prossimi problemi: non masterizzare cpu, non leggere stream di un byte ( come ha fatto readline), ecc

Ecco la mia implementazione https://gist.github.com/grubberr/5501e1a9760c3eab5e0a non supporta windows (poll), non gestisce EOF, ma funziona bene per me


la risposta filo-based non non brucia CPU (è possibile specificare arbitrario timeoutcome nella soluzione) e la .readline()legge più di un byte alla volta ( bufsize=1mezzo linea -buffered (rilevante solo per la scrittura)). Quali altri problemi hai riscontrato? Le risposte solo link non sono molto utili.
jfs,

0

Questo è un esempio per eseguire il comando interattivo in sottoprocesso e lo stdout è interattivo usando lo pseudo terminale. È possibile fare riferimento a: https://stackoverflow.com/a/43012138/3555925

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys
import select
import termios
import tty
import pty
from subprocess import Popen

command = 'bash'
# command = 'docker run -it --rm centos /bin/bash'.split()

# save original tty setting then set it to raw mode
old_tty = termios.tcgetattr(sys.stdin)
tty.setraw(sys.stdin.fileno())

# open pseudo-terminal to interact with subprocess
master_fd, slave_fd = pty.openpty()

# use os.setsid() make it run in a new process group, or bash job control will not be enabled
p = Popen(command,
          preexec_fn=os.setsid,
          stdin=slave_fd,
          stdout=slave_fd,
          stderr=slave_fd,
          universal_newlines=True)

while p.poll() is None:
    r, w, e = select.select([sys.stdin, master_fd], [], [])
    if sys.stdin in r:
        d = os.read(sys.stdin.fileno(), 10240)
        os.write(master_fd, d)
    elif master_fd in r:
        o = os.read(master_fd, 10240)
        if o:
            os.write(sys.stdout.fileno(), o)

# restore tty settings back
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)

0

Questa soluzione utilizza il selectmodulo per "leggere tutti i dati disponibili" da un flusso IO. Questa funzione si blocca inizialmente fino a quando i dati non sono disponibili, ma poi legge solo i dati disponibili e non li blocca ulteriormente.

Dato che utilizza il selectmodulo, questo funziona solo su Unix.

Il codice è completamente conforme a PEP8.

import select


def read_available(input_stream, max_bytes=None):
    """
    Blocks until any data is available, then all available data is then read and returned.
    This function returns an empty string when end of stream is reached.

    Args:
        input_stream: The stream to read from.
        max_bytes (int|None): The maximum number of bytes to read. This function may return fewer bytes than this.

    Returns:
        str
    """
    # Prepare local variables
    input_streams = [input_stream]
    empty_list = []
    read_buffer = ""

    # Initially block for input using 'select'
    if len(select.select(input_streams, empty_list, empty_list)[0]) > 0:

        # Poll read-readiness using 'select'
        def select_func():
            return len(select.select(input_streams, empty_list, empty_list, 0)[0]) > 0

        # Create while function based on parameters
        if max_bytes is not None:
            def while_func():
                return (len(read_buffer) < max_bytes) and select_func()
        else:
            while_func = select_func

        while True:
            # Read single byte at a time
            read_data = input_stream.read(1)
            if len(read_data) == 0:
                # End of stream
                break
            # Append byte to string buffer
            read_buffer += read_data
            # Check if more data is available
            if not while_func():
                break

    # Return read buffer
    return read_buffer

0

Ho anche affrontato il problema descritto da Jesse e risolto usando "select" come Bradley , Andy e altri hanno fatto, ma in una modalità di blocco per evitare un loop affollato. Usa un manichino Pipe come falso stdin. I blocchi selezionati e attendono che sia pronto lo stdin o la pipe. Quando viene premuto un tasto, stdin sblocca la selezione e il valore della chiave può essere recuperato con read (1). Quando un thread diverso scrive sul pipe, il pipe sblocca la selezione e può essere preso come indicazione che la necessità di stdin è finita. Ecco qualche codice di riferimento:

import sys
import os
from select import select

# -------------------------------------------------------------------------    
# Set the pipe (fake stdin) to simulate a final key stroke
# which will unblock the select statement
readEnd, writeEnd = os.pipe()
readFile = os.fdopen(readEnd)
writeFile = os.fdopen(writeEnd, "w")

# -------------------------------------------------------------------------
def getKey():

    # Wait for stdin or pipe (fake stdin) to be ready
    dr,dw,de = select([sys.__stdin__, readFile], [], [])

    # If stdin is the one ready then read it and return value
    if sys.__stdin__ in dr:
        return sys.__stdin__.read(1)   # For Windows use ----> getch() from module msvcrt

    # Must finish
    else:
        return None

# -------------------------------------------------------------------------
def breakStdinRead():
    writeFile.write(' ')
    writeFile.flush()

# -------------------------------------------------------------------------
# MAIN CODE

# Get key stroke
key = getKey()

# Keyboard input
if key:
    # ... do your stuff with the key value

# Faked keystroke
else:
    # ... use of stdin finished

# -------------------------------------------------------------------------
# OTHER THREAD CODE

breakStdinRead()

NOTA: per far sì che funzioni in Windows, la pipe deve essere sostituita da un socket. Non l'ho ancora provato ma dovrebbe funzionare secondo la documentazione.
gonzaedu61

0

Prova wexpect , che è l'alternativa di Windows di pexpect .

import wexpect

p = wexpect.spawn('myprogram.exe')
p.stdout.readline('.')               // regex pattern of any character
output_str = p.after()

0

Su sistemi simili a Unix e Python 3.5+ c'è quello os.set_blockingche fa esattamente quello che dice.

import os
import time
import subprocess

cmd = 'python3', '-c', 'import time; [(print(i), time.sleep(1)) for i in range(5)]'
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
os.set_blocking(p.stdout.fileno(), False)
start = time.time()
while True:
    # first iteration always produces empty byte string in non-blocking mode
    for i in range(2):    
        line = p.stdout.readline()
        print(i, line)
        time.sleep(0.5)
    if time.time() > start + 5:
        break
p.terminate()

Questo produce:

1 b''
2 b'0\n'
1 b''
2 b'1\n'
1 b''
2 b'2\n'
1 b''
2 b'3\n'
1 b''
2 b'4\n'

Con os.set_blockingcommentato è:

0 b'0\n'
1 b'1\n'
0 b'2\n'
1 b'3\n'
0 b'4\n'
1 b''

-2

Ecco un modulo che supporta letture non bloccanti e scritture in background in Python:

https://pypi.python.org/pypi/python-nonblock

Fornisce una funzione,

nonblock_read che leggerà i dati dallo stream, se disponibile, altrimenti restituirà una stringa vuota (o None se lo stream è chiuso dall'altra parte e tutti i dati possibili sono stati letti)

Puoi anche prendere in considerazione il modulo python-subprocess2,

https://pypi.python.org/pypi/python-subprocess2

che si aggiunge al modulo di sottoprocesso. Quindi sull'oggetto restituito da "subprocess.Popen" viene aggiunto un metodo aggiuntivo, runInBackground. Questo avvia un thread e restituisce un oggetto che verrà automaticamente popolato quando le cose vengono scritte su stdout / stderr, senza bloccare il thread principale.

Godere!


Mi piacerebbe provare questo modulo non- block , ma sono relativamente nuovo in alcune delle procedure Linux. Esattamente come installo queste routine? Sto eseguendo Raspbian Jessie, un sapore di Debian Linux per Raspberry Pi. Ho provato 'sudo apt-get install nonblock' e python-nonblock ed entrambi hanno lanciato un errore - non trovato. Ho scaricato il file zip da questo sito pypi.python.org/pypi/python-nonblock , ma non so che farsene. Grazie .... RDK
RDK
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.