Come reindirizzare l'output di "stampa" su un file usando Python?


184

Voglio reindirizzare la stampa su un file .txt usando Python. Ho un ciclo 'for', che 'stampa' l'output per ciascuno dei miei file .bam mentre voglio reindirizzare TUTTI questi output su un file. Quindi ho provato a mettere

 f = open('output.txt','w'); sys.stdout = f

all'inizio della mia sceneggiatura. Tuttavia non ottengo nulla nel file .txt. La mia sceneggiatura è:

#!/usr/bin/python

import os,sys
import subprocess
import glob
from os import path

f = open('output.txt','w')
sys.stdout = f

path= '/home/xug/nearline/bamfiles'
bamfiles = glob.glob(path + '/*.bam')

for bamfile in bamfiles:
    filename = bamfile.split('/')[-1]
    print 'Filename:', filename
    samtoolsin = subprocess.Popen(["/share/bin/samtools/samtools","view",bamfile],
                                  stdout=subprocess.PIPE,bufsize=1)
    linelist= samtoolsin.stdout.readlines()
    print 'Readlines finished!'
    ........print....
    ........print....

Allora, qual'è il problema? Qualche altro modo oltre a questo sys.stdout?

Ho bisogno che il mio risultato sia simile a:

Filename: ERR001268.bam
Readlines finished!
Mean: 233
SD: 10
Interval is: (213, 252)

7
Perché non usare f.write(data)?
Eran Zimmerman Gonen,

sì, ma ho diversi dati per ogni file bam (media, SD, intervallo ...), come posso mettere questi dati uno per uno?
LookIntoEast,

f.write(line)- inserisce un'interruzione di riga alla fine.
Eran Zimmerman Gonen,

8
@Eran Zimmerman: f.write(line)non aggiunge un'interruzione di linea ai dati.
hughdbrown,

Hai ragione, mia cattiva. Potrebbe sempre f.write(line+'\n'), comunque ...
Eran Zimmerman Gonen il

Risposte:


274

Il modo più ovvio per farlo sarebbe stampare su un oggetto file:

with open('out.txt', 'w') as f:
    print >> f, 'Filename:', filename     # Python 2.x
    print('Filename:', filename, file=f)  # Python 3.x

Tuttavia, il reindirizzamento di stdout funziona anche per me. Probabilmente va bene per uno script unico come questo:

import sys

orig_stdout = sys.stdout
f = open('out.txt', 'w')
sys.stdout = f

for i in range(2):
    print 'i = ', i

sys.stdout = orig_stdout
f.close()

Il reindirizzamento esternamente dalla shell stessa è un'altra buona opzione:

./script.py > out.txt

Altre domande:

Qual è il primo nome file nel tuo script? Non lo vedo inizializzato.

La mia prima ipotesi è che glob non trovi alcun bamfile, e quindi il ciclo for non funziona. Verifica che la cartella esista e stampa i bamfile nello script.

Inoltre, utilizzare os.path.join e os.path.basename per manipolare percorsi e nomi di file.


La riga 8 del codice utilizza una variabile denominata nomefile, ma non è stata ancora creata. Più avanti nel loop lo usi di nuovo, ma non è rilevante.
Gringo Suave,

2
Cattiva pratica per cambiare sys.stdout se non è necessario.
desiderio di macchina il

3
@mio non sono convinto che sia un male per una sceneggiatura semplice come questa.
Gringo Suave,

4
+1 Ahah bene puoi avere il mio voto perché è il modo giusto di farlo se devi assolutamente farlo nel modo sbagliato ... Ma dico ancora che dovresti farlo con un normale output di file.
desiderio di macchina il

1
Come reindirizzare e stampare l'output sulla console? Sembra che "print ()" in Python non possa essere mostrato quando lo stdrr viene reindirizzato?
esterno

70

È possibile reindirizzare la stampa con l' >>operatore.

f = open(filename,'w')
print >>f, 'whatever'     # Python 2.x
print('whatever', file=f) # Python 3.x

Nella maggior parte dei casi, è meglio scrivere normalmente sul file.

f.write('whatever')

oppure, se hai diversi elementi che vuoi scrivere con spazi tra, come print:

f.write(' '.join(('whatever', str(var2), 'etc')))

2
Se ci sono molte istruzioni di output, queste possono invecchiare velocemente. L'idea originale dei poster è valida; c'è qualcos'altro che non va nello script.
Gringo Suave,

1
L'idea originale di Poster non è assolutamente valida. Non c'è motivo di reindirizzare stdout qui, poiché ha già i dati in una variabile.
desiderio di macchina il

Penso che intendesse "tecnicamente valido", in quanto puoi, in effetti, reindirizzare sys.stdout, non che fosse una buona idea.
AGF

35

Riferimento API Python 2 o Python 3 :

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

L' argomento file deve essere un oggetto con un write(string)metodo; se non è presente o None, sys.stdoutverrà utilizzato. Poiché gli argomenti stampati vengono convertiti in stringhe di testo, print()non possono essere utilizzati con oggetti file in modalità binaria. Per questi, utilizzare file.write(...)invece.

Poiché l' oggetto file normalmente contiene il write()metodo, tutto ciò che devi fare è passare un oggetto file nel suo argomento.

Scrivi / Sovrascrivi su file

with open('file.txt', 'w') as f:
    print('hello world', file=f)

Scrivi / Aggiungi al file

with open('file.txt', 'a') as f:
    print('hello world', file=f)

2
Ho appena confuso il motivo per cui alcune di quelle risposte precedenti erano di patch patch globale sys.stdout:(
Yeo

35

Funziona perfettamente:

import sys
sys.stdout=open("test.txt","w")
print ("hello")
sys.stdout.close()

Ora il saluto verrà scritto nel file test.txt. Assicurati di chiudere stdoutcon a close, senza di esso il contenuto non verrà salvato nel file


3
ma anche se ci esibiamosys.stdout.close() , se digiti qualcosa nella shell di Python mostrerà l'errore come ValueError: I/O operation on closed file. imgur.com/a/xby9P . Il modo migliore per gestirlo è seguire quanto pubblicato da @Gringo Suave
Mourya il

24

Non usare print, usalogging

È possibile cambiare sys.stdoutper indicare un file, ma questo è un modo piuttosto ingombrante e poco flessibile per gestire questo problema. Invece di usare print, usa il loggingmodulo.

Con logging, puoi stampare proprio come faresti stdout, oppure puoi anche scrivere l'output in un file. È anche possibile utilizzare i diversi livelli di messaggi ( critical, error, warning, info, debug) per, ad esempio, stampare solo grandi temi alla console, ma ancora registrare le azioni di codici minori in un file.

Un semplice esempio

Importa logging, ottieni il loggere imposta il livello di elaborazione:

import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG) # process everything, even if everything isn't printed

Se vuoi stampare su stdout:

ch = logging.StreamHandler()
ch.setLevel(logging.INFO) # or any other level
logger.addHandler(ch)

Se vuoi scrivere anche su un file (se vuoi solo scrivere su un file salta l'ultima sezione):

fh = logging.FileHandler('myLog.log')
fh.setLevel(logging.DEBUG) # or any level you want
logger.addHandler(fh)

Quindi, ovunque tu utilizzi, printusa uno dei loggermetodi:

# print(foo)
logger.debug(foo)

# print('finishing processing')
logger.info('finishing processing')

# print('Something may be wrong')
logger.warning('Something may be wrong')

# print('Something is going really bad')
logger.error('Something is going really bad')

Per saperne di più sull'utilizzo di funzionalità più avanzate logging, leggi l'eccellente loggingtutorial nei documenti di Python .


Salve, desidero utilizzare questa registrazione per scrivere i dati della console nel file di registro con l'ora del momento in cui i dati vengono acquisiti. Ma non sono in grado di comprendere correttamente la funzione di registrazione o la libreria. Potete aiutarmi in questo
haris

@haris Leggi il tutorial di registrazione dei documenti di Python e controlla gli esempi in altre domande su Stack Overflow (ce ne sono molti). Se non riesci ancora a farlo funzionare, fai una nuova domanda.
jpyams,

12

La soluzione più semplice non è tramite Python; è attraverso il guscio. Dalla prima riga del tuo file ( #!/usr/bin/python) immagino che tu sia su un sistema UNIX. Usa semplicemente le printistruzioni come faresti normalmente e non aprire affatto il file nel tuo script. Quando vai a eseguire il file, invece di

./script.py

per eseguire il file, utilizzare

./script.py > <filename>

dove si sostituisce <filename>con il nome del file in cui si desidera inserire l'output. Il >token dice alla maggior parte delle shell di impostare stdout sul file descritto dal token seguente.

Una cosa importante che deve essere menzionata qui è che "script.py" deve essere reso eseguibile per ./script.pyessere eseguito.

Quindi, prima di eseguire ./script.py, eseguire questo comando

chmod a+x script.py (rendere lo script eseguibile per tutti gli utenti)


3
./script.py> <nomefile> 2> & 1 Devi catturare anche stderr. 2> & 1 lo faranno
dopo il

1
@rtaft Perché? La domanda in particolare vuole reindirizzare l'output di printun file. Sarebbe ragionevole aspettarsi che stdout (tracce dello stack e simili) continui a stampare sul terminale.
Aaron Dufour,

Ha detto che non funzionava, neanche il mio funzionava. In seguito ho scoperto che questa app su cui sto lavorando era configurata per indirizzare tutto su stderr ... idk perché.
Dall'8

5

Se stai usando Linux ti suggerisco di usare il teecomando. L'implementazione è la seguente:

python python_file.py | tee any_file_name.txt

Se non vuoi cambiare nulla nel codice, penso che questa potrebbe essere la migliore soluzione possibile. È inoltre possibile implementare il logger ma è necessario apportare alcune modifiche al codice.


1
grande; lo cercavo
Vicrobot il

4

Potrebbe non piacerti questa risposta, ma penso che sia quella GIUSTA. Non cambiare la destinazione stdout a meno che non sia assolutamente necessario (forse stai usando una libreria che produce solo stdout ??? chiaramente non è il caso qui).

Penso che sia una buona abitudine che tu debba preparare i tuoi dati in anticipo come una stringa, quindi aprire il tuo file e scrivere tutto in una volta. Questo perché le operazioni di input / output sono più lunghe hai un handle di file aperto, più è probabile che si verifichi un errore con questo file (errore di blocco file, errore I / o, ecc.). Fare tutto in una sola operazione non lascia dubbi su quando potrebbe essere andato storto.

Ecco un esempio:

out_lines = []
for bamfile in bamfiles:
    filename = bamfile.split('/')[-1]
    out_lines.append('Filename: %s' % filename)
    samtoolsin = subprocess.Popen(["/share/bin/samtools/samtools","view",bamfile],
                                  stdout=subprocess.PIPE,bufsize=1)
    linelist= samtoolsin.stdout.readlines()
    print 'Readlines finished!'
    out_lines.extend(linelist)
    out_lines.append('\n')

E poi, una volta terminata la raccolta delle "righe dati" di una riga per elemento dell'elenco, è possibile unirle con alcuni '\n'caratteri per rendere il tutto eseguibile; forse anche racchiudere la dichiarazione di output in un withblocco, per una maggiore sicurezza (chiuderà automaticamente l'handle di output anche se qualcosa va storto):

out_string = '\n'.join(out_lines)
out_filename = 'myfile.txt'
with open(out_filename, 'w') as outf:
    outf.write(out_string)
print "YAY MY STDOUT IS UNTAINTED!!!"

Tuttavia, se hai un sacco di dati da scrivere, si potrebbe scrivere un pezzo alla volta. Non credo sia rilevante per la tua applicazione, ma ecco l'alternativa:

out_filename = 'myfile.txt'
outf = open(out_filename, 'w')
for bamfile in bamfiles:
    filename = bamfile.split('/')[-1]
    outf.write('Filename: %s' % filename)
    samtoolsin = subprocess.Popen(["/share/bin/samtools/samtools","view",bamfile],
                                  stdout=subprocess.PIPE,bufsize=1)
    mydata = samtoolsin.stdout.read()
    outf.write(mydata)
outf.close()

1
Con la memorizzazione nella cache del disco, le prestazioni dell'originale dovrebbero essere accettabili. Questa soluzione presenta tuttavia l'inconveniente di sovrapporre i requisiti di memoria in caso di output elevato. Anche se probabilmente nulla di cui preoccuparsi qui, è generalmente una buona idea evitarlo, se possibile. Stessa idea di usare xrange (intervallo py3) invece di intervallo, ecc.
Gringo Suave,

@Gringo: non ha specificato questo requisito. Raramente scrivo mai abbastanza dati su un file da renderlo rilevante. Questa non è la stessa idea di xrange perché xrange non si occupa degli I / O dei file. La memorizzazione nella cache del disco potrebbe essere d'aiuto, ma è comunque una cattiva pratica mantenere aperto un handle di file per un ampio corpus di codice.
desiderio di macchina il

1
Il tuo commento si contraddice. Ad essere onesti, l'aspetto prestazionale di entrambi gli approcci è irrilevante per quantità non enormi di dati. xrange è certamente simile, funziona su un pezzo alla volta anziché su tutti contemporaneamente in memoria. Forse un generatore vs elenco è un esempio migliore però.
Gringo Suave,

@Gringo: non riesco a vedere come il mio commento si contraddice. Forse l'aspetto delle prestazioni non è rilevante, mantenendo un handle di file aperto per un lungo periodo aumenta sempre il rischio di errore. Nella programmazione di file i / o è sempre intrinsecamente più rischioso che fare qualcosa all'interno del proprio programma, perché significa che è necessario raggiungere il sistema operativo e pasticciare con i blocchi dei file. Più corto è un file aperto, meglio è, semplicemente perché non controlli il file system dal tuo codice. xrange è diverso perché non ha nulla a che fare con l'I / O dei file e, FYI, raramente uso anche xrange; evviva
desiderio di macchina il

2
@Gringo: apprezzo le tue critiche e mi è piaciuto il acceso dibattito. Anche se non siamo d'accordo su alcuni punti, continuo a rispettare le tue opinioni in quanto è chiaro che hai una buona ragione per assumere la tua posizione. Grazie per averlo concluso ragionevolmente e buona notte. : P
desiderio di macchina il

2

Se il reindirizzamento stdoutfunziona per il tuo problema, la risposta di Gringo Suave è una buona dimostrazione di come farlo.

Per renderlo ancora più semplice , ho creato una versione utilizzando i gestori di contesto per una sintassi sintetica di chiamata sintetica usando l' withistruzione:

from contextlib import contextmanager
import sys

@contextmanager
def redirected_stdout(outstream):
    orig_stdout = sys.stdout
    try:
        sys.stdout = outstream
        yield
    finally:
        sys.stdout = orig_stdout

Per usarlo, basta fare quanto segue (derivato dall'esempio di Suave):

with open('out.txt', 'w') as outfile:
    with redirected_stdout(outfile):
        for i in range(2):
            print('i =', i)

È utile per reindirizzare selettivamente printquando un modulo lo utilizza in un modo che non ti piace. L'unico svantaggio (e questo è il dealbreaker per molte situazioni) è che non funziona se si vogliono più thread con valori diversi di stdout, ma ciò richiede un metodo migliore e più generalizzato: l'accesso al modulo indiretto. Puoi vedere le implementazioni di ciò in altre risposte a questa domanda.


0

La modifica del valore di sys.stdout modifica la destinazione di tutte le chiamate da stampare. Se usi un modo alternativo per cambiare la destinazione di stampa, otterrai lo stesso risultato.

Il tuo bug è altrove:

  • potrebbe essere nel codice che hai rimosso per la tua domanda (da dove viene il nome file per aprire la chiamata?)
  • potrebbe anche non essere in attesa che i dati vengano scaricati: se si stampa su un terminale, i dati vengono scaricati dopo ogni nuova riga, ma se si stampa su un file, vengono scaricati solo quando il buffer stdout è pieno (4096 byte sulla maggior parte dei sistemi).

-1

Qualcosa per estendere la funzione di stampa per i loop

x = 0
while x <=5:
    x = x + 1
    with open('outputEis.txt', 'a') as f:
        print(x, file=f)
    f.close()

non è necessario utilizzare whilee non è necessario chiudere il file quando si utilizzawith
Daniel Stracaboško,
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.