2015-12-25 25 views
7

Sommario: Mi piacerebbe scrivere script Python che si comportano come script bash sulla riga di comando, ma poi mi piacerebbe anche collegarli insieme facilmente in python. Dove sto avendo problemi è la colla per far accadere quest'ultimo.Come rendere gli script Python pipe-able sia in bash che all'interno di python

quindi immaginate ho scritto due sceneggiature, script1.py e script2.py e posso tubo insieme in questo modo:

echo input_string | ./script1.py -a -b | ./script2.py -c -d 

Come faccio ad avere questo comportamento all'interno di un altro file Python? Ecco il modo in cui lo so, ma non mi piacciono:

arg_string_1 = convert_to_args(param_1, param_2) 
arg_string_2 = convert_to_args(param_3, param_4) 
output_string = subprocess.check_output("echo " + input_string + " | ./script1.py " + arg_string_1 + " | ./script2.py " + arg_string_2) 

Se non volevo approfittare di multithreading, avrei potuto fare qualcosa di simile (?):

input1 = StringIO(input_string) 
output1 = StringIO() 
script1.main(param_1, param_2, input1, output1) 
input2 = StringIO(output1.get_value()) 
output2 = StringIO() 
script2.main(param_3, param_4, input2, output2) 

Ecco l'approccio che stavo cercando, ma mi sono bloccato a scrivere la colla. Mi piacerebbe che imparassi come finire il mio approccio qui sotto, o suggerimenti per un migliore design/approccio!

Il mio approccio: Ho scritto script1.py e script2.py a guardare come:

#!/usr/bin/python3 

... # import sys and define "parse_args" 

def main(param_1, param_2, input, output): 
    for line in input: 
    ... 
    print(stuff, file=output) 

if __name__ == "__main__": 
    parameter_1, parameter_2 = parse_args(sys.argv) 
    main(parameter_1, parameter_2, sys.stdin, sys.stdout) 

Poi ho voluto scrivere qualcosa di simile, ma non so come finire:

pipe_out, pipe_in = ???? 
output = StringIO() 
thread_1 = Thread(target=script1.main, args=(param_1, param_2, StreamIO(input_string), pipe_out)) 
thread_2 = Thread(target=script2.main, args=(param_3, param_4, pipe_in, output) 
thread_1.start() 
thread_2.start() 
thread_1.join() 
thread_2.join() 
output_str = output.get_value() 

risposta

1

Per la "pipe in", utilizza sys.stdin con il metodo readlines(). (Utilizzare il metodo read() per leggere un carattere alla volta.)

Per passare informazioni da una discussione a un'altra, è possibile utilizzare Queue. È necessario definire un modo per segnalare la fine dei dati. Nel mio esempio, poiché tutti i dati passati tra i thread sono str, uso semplicemente un oggetto None per segnalare la fine dei dati (poiché non può apparire nei dati trasmessi).

Si potrebbe anche utilizzare più thread o utilizzare funzioni diverse nei thread.

Nel mio esempio non ho incluso lo sys.argv per mantenerlo semplice. La modifica per ottenere i parametri (parameter1, ...) dovrebbe essere semplice.

import sys 
from threading import Thread 
from Queue import Queue 
import fileinput 

def stdin_to_queue(output_queue): 
    for inp_line in sys.stdin.readlines():  # input one line at at time             
    output_queue.put(inp_line, True, None) # blocking, no timeout 
    output_queue.put(None, True, None) # signal the end of data             


def main1(input_queue, output_queue, arg1, arg2): 
    do_loop = True 
    while do_loop: 
    inp_data = input_queue.get(True) 
    if inp_data is None: 
     do_loop = False 
     output_queue.put(None, True, None) # signal end of data              
    else: 
     out_data = arg1 + inp_data.strip('\r\n').upper() + arg2 # or whatever transformation...          
     output_queue.put(out_data, True, None) 

def queue_to_stdout(input_queue): 
    do_loop = True 
    while do_loop: 
    inp_data = input_queue.get(True) 
    if inp_data is None: 
     do_loop = False 
    else: 
     sys.stdout.write(inp_data) 


def main(): 
    q12 = Queue() 
    q23 = Queue() 
    q34 = Queue() 
    t1 = Thread(target=stdin_to_queue, args=(q12,)) 
    t2 = Thread(target=main1, args=(q12,q23,'(',')')) 
    t3 = Thread(target=main1, args=(q23,q34,'[',']')) 
    t4 = Thread(target=queue_to_stdout, args=(q34,)) 
    t1.start() 
    t2.start() 
    t3.start() 
    t4.start() 


main() 

Infine, ho testato questo programma (python2) con un file di testo.

head sometextfile.txt | python script.py 
+0

Grazie, questo sembra buono, solo un sacco di codice. Speravo che potesse esserci qualcosa di più conciso. Ma se la performance è buona, potrebbe valerne la pena. – usul

+0

Ci sono molti modi per ridurlo. Ad esempio, puoi leggere direttamente da un file e scrivere direttamente in un file all'interno dello stesso thread. Ho separato di proposito un sacco di cose per illustrare molte cose individualmente. –

1

reindirizzare il valore restituito stdout seconda che lo script è stato eseguito dalla riga di comando:

#!/usr/bin/python3 
import sys 

# Example function 
def main(input): 
    # Do something with input producing stuff 
    ... 
    return multipipe(stuff) 

if __name__ == '__main__': 
    def multipipe(data): 
     print(data) 

    input = parse_args(sys.argv) 
    main(input) 
else: 
    def multipipe(data): 
     return data 

Ogni altro script avrà gli stessi due definizioni di multipipe. Ora, utilizzare multipipe per l'output.

Se si chiama tutti gli script insieme dalla riga di comando $ ./scrip1.py | ./scrip2.py, ognuno avrà __name__ == '__main__' e così multipipe stamperà tutto a stdout da leggere come un argomento per il prossimo script (e tornare None, quindi ogni funzione restituisce None , ma in questo caso non stai guardando i valori di ritorno).

Se li chiamate all'interno di un altro script python, ciascuna funzione restituirà tutto ciò che è passato a multipipe.

In modo efficace, è possibile utilizzare le funzioni esistenti, basta sostituire print(stuff, file=output) con return multipipe(stuff). Bello e semplice

Per utilizzarlo con multithreading o multiprocessing, impostare le funzioni in modo che ogni funzione restituisca una singola cosa e inserirle in una semplice funzione che aggiunge dati a una coda di multithreading. Per un esempio di tale sistema di code, vedi the sample at the bottom of the Queue docs. Con questo esempio, assicurati che ogni passaggio nella pipeline contenga None (o altro valore sentinella di tua scelta - Mi piace lo ... poiché è estremamente raro che tu passi l'oggetto Ellipsis per qualsiasi motivo tranne che come indicatore per il suo singleton-ness) nella coda a quello successivo per indicare il fatto.

0

Esiste una soluzione molto semplice che utilizza la classe standard Popen.

Ecco un esempio:

#this is the master python program 
import subprocess 
import sys 
import os 

#note the use of stdin and stdout arguments here 
process1 = subprocess.Popen(['./script1.py'], stdin=sys.stdin, stdout=subprocess.PIPE) 
process2 = subprocess.Popen(['./script2.py'], stdin=process1.stdout) 

process1.wait() 
process2.wait() 

i due script sono:

#!/usr/bin/env python 
#script1.py 
import sys 

for line in sys.stdin: 
    print(line.strip().upper()) 

Ecco la seconda

#!/usr/bin/env python 
#script2.py 
import sys 

for line in sys.stdin: 
    print("<{}>".format(line.strip())) 
+0

Grazie, è decisamente meglio della mia soluzione. Resta comunque aperto come gestire i parametri. Questa soluzione sembra richiedere di prendere un oggetto parametro, convertirlo in una stringa, passarlo attraverso Popen e quindi process1 analizza la stringa e ricrea l'oggetto. Sarebbe bello passare direttamente attraverso gli oggetti. – usul

+0

I parametri @usul non sono un problema, basta mettere '['./script1.py', 'param1', 'param2']' nella chiamata a Popen invece di solo '['./script1.py']' –

+0

Ciao @ Sì, sì, funziona bene se i parametri sono già stringhe. Ma non è bello se sono liste o oggetti più complicati. Ad esempio, se ho un oggetto datetime, devo prima convertirlo in una stringa, quindi passarlo, quindi script1.py deve analizzarlo nuovamente in un oggetto datetime. – usul