¿Cómo redirigir la salida 'imprimir' a un archivo usando Python?

184

Quiero redirigir la impresión a un archivo .txt usando python. Tengo un bucle 'for', que 'imprimirá' la salida de cada uno de mis archivos .bam mientras quiero redirigir TODAS estas salidas a un archivo. Entonces traté de poner

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

Al principio de mi guión. Sin embargo, no obtengo nada en el archivo .txt. Mi guión es:

#!/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....

¿Entonces, cuál es el problema? ¿De alguna otra manera además de este sys.stdout?

Necesito que mi resultado se vea así:

Filename: ERR001268.bam
Readlines finished!
Mean: 233
SD: 10
Interval is: (213, 252)
LookIntoEast
fuente
77
¿Por qué no usar f.write(data)?
Eran Zimmerman Gonen
sí, pero tengo varios datos para cada archivo bam (media, SD, intervalo ...), ¿cómo puedo poner estos datos uno por uno?
LookIntoEast
f.write(line)- inserta un salto de línea al final.
Eran Zimmerman Gonen
8
@Eran Zimmerman: f.write(line)no agrega un salto de línea a los datos.
hughdbrown
Tienes razón, mi mal. f.write(line+'\n')Sin embargo, siempre podría ...
Eran Zimmerman Gonen

Respuestas:

274

La forma más obvia de hacer esto sería imprimir en un objeto de archivo:

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

Sin embargo, redireccionar stdout también funciona para mí. Probablemente esté bien para un script único como este:

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()

Redirigir externamente desde el propio shell es otra buena opción:

./script.py > out.txt

Otras preguntas:

¿Cuál es el primer nombre de archivo en su script? No lo veo inicializado.

Mi primera suposición es que glob no encuentra ningún archivo bam y, por lo tanto, el bucle for no se ejecuta. Compruebe que la carpeta existe e imprima archivos bam en su secuencia de comandos.

Además, use os.path.join y os.path.basename para manipular rutas y nombres de archivos.

Gringo Suave
fuente
La línea 8 de su código usa una variable llamada nombre de archivo, pero aún no se ha creado. Más adelante en el ciclo, lo usa de nuevo, pero no es relevante.
Gringo Suave
2
Mala práctica para cambiar sys.stdout si no es necesario.
máquina anhelando el
3
@my No estoy convencido de que sea malo para un script simple como este.
Gringo Suave
44
+1 Jaja, bueno, puedes tener mi voto a favor porque es la forma correcta de hacerlo si absolutamente debes hacerlo de la manera incorrecta ... Pero todavía digo que debes hacerlo con la salida de archivos normal.
máquina anhelando el
1
¿Cómo redirigir e imprimir la salida en la consola? Parece que "print ()" en Python no se puede mostrar cuando se redirige el stdrr?
Externa
70

Puede redirigir la impresión con el >>operador.

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

En la mayoría de los casos, es mejor simplemente escribir en el archivo normalmente.

f.write('whatever')

o, si tiene varios elementos que desea escribir con espacios entre ellos, como print:

f.write(' '.join(('whatever', str(var2), 'etc')))
agf
fuente
2
Si hay muchas declaraciones de salida, estas pueden envejecer rápidamente. La idea original de los carteles es válida; Hay algo más mal con el guión.
Gringo Suave
1
La idea original del póster es absolutamente inválida. No hay ninguna razón para redirigir stdout aquí, ya que él ya obtiene los datos en una variable.
máquina anhelando el
Creo que quiso decir "técnicamente válido", en el sentido de que puede, de hecho, redirigir sys.stdout, no es una buena idea.
agf
35

Referencia de API de Python 2 o Python 3 :

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

El argumento del archivo debe ser un objeto con un write(string)método; si no está presente o None, sys.stdoutse utilizará. Como los argumentos impresos se convierten en cadenas de texto, print()no se pueden usar con objetos de archivo en modo binario. Para estos, use file.write(...)en su lugar.

Dado que el objeto de archivo normalmente contiene un write()método, todo lo que necesita hacer es pasar un objeto de archivo a su argumento.

Escribir / sobrescribir en archivo

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

Escribir / agregar al archivo

with open('file.txt', 'a') as f:
    print('hello world', file=f)
Yeo
fuente
2
Acabo de confundir por qué algunas de esas respuestas anteriores fueron parchear el global sys.stdout:(
Yeo
35

Esto funciona perfectamente:

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

Ahora el saludo se escribirá en el archivo test.txt. Asegúrese de cerrar el archivo stdoutcon a close, sin él el contenido no se guardará en el archivo

Pradeep Kumar
fuente
3
pero incluso si lo hacemos sys.stdout.close(), si escribe algo en Python Shell, mostrará el error como ValueError: I/O operation on closed file. imgur.com/a/xby9P . La mejor manera de manejar esto es seguir lo que @Gringo Suave publicó
Mourya,
24

No uses print, usalogging

Puede cambiar sys.stdoutpara apuntar a un archivo, pero esta es una forma bastante torpe e inflexible de manejar este problema. En lugar de usar print, use el loggingmódulo.

Con logging, puede imprimir como lo haría stdout, o también puede escribir la salida en un archivo. Usted puede incluso utilizar los diferentes niveles de mensaje ( critical, error, warning, info, debug) para, por ejemplo, sólo imprimir las principales cuestiones a la consola, pero aún así registrar las acciones de código de menor importancia a un archivo.

Un simple ejemplo

Importe logging, obtenga loggery establezca el nivel de procesamiento:

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

Si desea imprimir en stdout:

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

Si también desea escribir en un archivo (si solo desea escribir en un archivo, omita la última sección):

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

Luego, donde sea que use printusar uno de los loggermétodos:

# 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')

Para obtener más información sobre el uso de loggingfunciones más avanzadas , lea el excelente loggingtutorial en los documentos de Python .

jpyams
fuente
Hola, quiero usar este registro para escribir los datos de la consola en el archivo de registro con la hora en que se toman esos datos. Pero no puedo entender la función de registro o la biblioteca correctamente. ¿Me pueden ayudar con esto
Haris
@haris Lea el tutorial de registro de documentos de Python y vea ejemplos en otras preguntas sobre Stack Overflow (hay muchos de ellos). Si aún no puede hacer que funcione, haga una nueva pregunta.
jpyams
12

La solución más fácil no es a través de Python; es a través de la concha. Desde la primera línea de su archivo ( #!/usr/bin/python) supongo que está en un sistema UNIX. Simplemente use printdeclaraciones como lo haría normalmente, y no abra el archivo en absoluto en su script. Cuando vayas a ejecutar el archivo, en lugar de

./script.py

para ejecutar el archivo, use

./script.py > <filename>

donde reemplaza <filename>con el nombre del archivo al que desea que vaya la salida. El >token le dice a (la mayoría) los shells que establezcan stdout en el archivo descrito por el siguiente token.

Una cosa importante que debe mencionarse aquí es que "script.py" debe hacerse ejecutable para ./script.pyejecutarse.

Entonces, antes de ejecutar ./script.py, ejecute este comando

chmod a+x script.py (hacer que el script sea ejecutable para todos los usuarios)

Aaron Dufour
fuente
3
./script.py> <nombre de archivo> 2> & 1 También necesita capturar stderr. 2> y 1 lo harán
rtaft
1
@rtaft ¿Por qué? La pregunta específicamente quiere canalizar la salida de printun archivo. Sería razonable esperar que stdout (trazas de pila y similares) aún se imprima en el terminal.
Aaron Dufour
Dijo que no estaba funcionando, el mío tampoco estaba funcionando. Más tarde descubrí que esta aplicación en la que estoy trabajando estaba configurada para dirigir todo a stderr ... idk por qué.
partir del
5

Si está utilizando Linux, le sugiero que use el teecomando. La implementación es así:

python python_file.py | tee any_file_name.txt

Si no desea cambiar nada en el código, creo que esta podría ser la mejor solución posible. También puede implementar el registrador, pero necesita hacer algunos cambios en el código.

Yunus
fuente
1
Excelente; lo estaba buscando
Vicrobot
4

Puede que no te guste esta respuesta, pero creo que es la CORRECTA. No cambie su destino stdout a menos que sea absolutamente necesario (tal vez esté usando una biblioteca que solo da salida a stdout, claramente no es el caso aquí).

Creo que, como buen hábito, debe preparar sus datos con anticipación como una cadena, luego abrir su archivo y escribir todo de una vez. Esto se debe a que las operaciones de entrada / salida son cuanto más tiempo tenga abierto un identificador de archivo, es más probable que ocurra un error con este archivo (error de bloqueo de archivo, error de E / S, etc.). Simplemente hacerlo todo en una sola operación no deja dudas sobre cuándo podría haber salido mal.

Aquí hay un ejemplo:

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')

Y luego, cuando haya terminado de recopilar sus "líneas de datos" una línea por elemento de la lista, puede unirlas con algunos '\n'caracteres para que todo se pueda generar; tal vez incluso envuelva su declaración de salida en unwith bloque, para mayor seguridad (cerrará automáticamente su identificador de salida incluso si algo sale mal):

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!!!"

Sin embargo, si tiene muchos datos para escribir, puede escribirlos de a uno por vez. No creo que sea relevante para su aplicación, pero esta es la 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()
anhelo de máquina
fuente
1
Con el almacenamiento en caché de disco, el rendimiento del original debe ser aceptable. Sin embargo, esta solución tiene el inconveniente de aumentar los requisitos de memoria si hubiera mucha salida. Aunque probablemente no haya nada de qué preocuparse aquí, generalmente es una buena idea evitar esto si es posible. La misma idea que usar xrange (rango py3) en lugar de rango, etc.
Gringo Suave
@Gringo: No especificó este requisito. Raramente escribo suficientes datos en un archivo para que esto sea relevante. Esta no es la misma idea que xrange porque xrange no se ocupa de la E / S de archivos. El almacenamiento en caché de disco puede ayudar, pero sigue siendo una mala práctica mantener abierto un identificador de archivo para un gran cuerpo de código.
máquina anhelando
1
Tu comentario se contradice a sí mismo. Para ser honesto, el aspecto de rendimiento de ambos enfoques es irrelevante para cantidades no enormes de datos. xrange ciertamente es similar, funciona en una pieza a la vez en lugar de todas a la vez en la memoria. Sin embargo, quizás un generador vs una lista sea un mejor ejemplo.
Gringo Suave
@Gringo: No veo cómo mi comentario se contradice. Tal vez el aspecto del rendimiento no sea relevante, mantener un identificador de archivo abierto durante un período prolongado siempre aumenta el riesgo de error. En el archivo de programación, la E / S siempre es inherentemente más arriesgada que hacer algo dentro de su propio programa, porque significa que tiene que llegar a través del sistema operativo y perder el tiempo con bloqueos de archivos. Cuanto más corto tenga abierto un archivo, mejor, simplemente porque no controla el sistema de archivos desde su código. xrange es diferente porque no tiene nada que ver con el archivo de E / S, y para su información rara vez uso xrange tampoco; aplausos
máquina anhelo
2
@Gringo: agradezco sus críticas y disfruté el acalorado debate. Aunque no estuvimos de acuerdo en algunos puntos, todavía respeto sus puntos de vista, ya que está claro que tiene una buena razón para adoptar su postura. Gracias por terminarlo razonablemente y que tengan una muy buena noche. : P
máquina anhelando el
2

Si la redirección stdoutfunciona para su problema, la respuesta de Gringo Suave es una buena demostración de cómo hacerlo.

Para hacerlo aún más fácil , hice una versión utilizando contextmanagers para una sintaxis de llamada generalizada sucinta usando la withdeclaración:

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

Para usarlo, simplemente haga lo siguiente (derivado del ejemplo de Suave):

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

Es útil para redirigir selectivamente printcuando un módulo lo usa de una manera que no le gusta. La única desventaja (y este es el factor decisivo para muchas situaciones) es que no funciona si uno quiere múltiples hilos con diferentes valores de stdout, pero eso requiere un método mejor y más generalizado: acceso indirecto al módulo. Puede ver implementaciones de eso en otras respuestas a esta pregunta.

Graham
fuente
0

Cambiar el valor de sys.stdout cambia el destino de todas las llamadas a imprimir. Si utiliza una forma alternativa de cambiar el destino de la impresión, obtendrá el mismo resultado.

Tu error está en otro lugar:

  • podría estar en el código que eliminó para su pregunta (¿de dónde proviene el nombre de archivo para que se abra la llamada?)
  • También podría ser que no está esperando que se vacíen los datos: si imprime en un terminal, los datos se vacían después de cada nueva línea, pero si imprime en un archivo, solo se vacían cuando el búfer stdout está lleno (4096 bytes en la mayoría de los sistemas).
Jerome
fuente
-1

Algo para extender la función de impresión para bucles

x = 0
while x <=5:
    x = x + 1
    with open('outputEis.txt', 'a') as f:
        print(x, file=f)
    f.close()
ishiry ish
fuente
no es necesario usar whiley no es necesario cerrar el archivo cuando se usawith
Daniel Stracaboško