Como redirecionar a saída 'print' para um arquivo usando python?

184

Quero redirecionar a impressão para um arquivo .txt usando python. Eu tenho um loop 'for', que 'imprimirá' a saída para cada um dos meus arquivos .bam enquanto eu quero redirecionar TODAS essas saídas para um arquivo. Então eu tentei colocar

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

no começo do meu roteiro. No entanto, não recebo nada no arquivo .txt. Meu script é:

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

Então qual é o problema? Alguma outra maneira além deste sys.stdout?

Eu preciso do meu resultado parecido com:

Filename: ERR001268.bam
Readlines finished!
Mean: 233
SD: 10
Interval is: (213, 252)
LookIntoEast
fonte
7
Por que não usar f.write(data)?
Eran Zimmerman Gonen
sim, mas tenho vários dados para cada arquivo bam (média, SD, intervalo ...), como posso colocar esses dados um por um?
LookIntoEast
f.write(line)- insere uma quebra de linha no final.
Eran Zimmerman Gonen
8
@Eran Zimmerman: f.write(line)não adiciona uma quebra de linha aos dados.
precisa saber é o seguinte
Você está certo, meu mal. Poderia sempre f.write(line+'\n'), no entanto ..
Eran Zimmerman Gonen

Respostas:

274

A maneira mais óbvia de fazer isso seria imprimir em um objeto de arquivo:

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

No entanto, redirecionar o stdout também funciona para mim. Provavelmente é bom para um 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()

Redirecionar externamente a partir do próprio shell é outra boa opção:

./script.py > out.txt

Outras perguntas:

Qual é o primeiro nome do arquivo no seu script? Não o vejo inicializado.

Meu primeiro palpite é que a glob não encontra nenhum arquivo bamfiles e, portanto, o loop for não roda. Verifique se a pasta existe e imprima bamfiles no seu script.

Além disso, use os.path.join e os.path.basename para manipular caminhos e nomes de arquivos.

Gringo Suave
fonte
A linha 8 do seu código usa uma variável chamada filename, mas ainda não foi criada. Posteriormente no loop, você o utiliza novamente, mas não é relevante.
Gringo Suave
2
Má prática para alterar o sys.stdout, se você não precisar.
anseio da máquina
3
@my não estou convencido de que é ruim para um script simples como este.
Gringo Suave
4
+1 Haha, bem, você pode ter o meu voto positivo, porque é o caminho certo para fazê-lo, se você absolutamente deve fazê-lo da maneira errada ... Mas eu ainda digo que você deve fazê-lo com saída de arquivo regular.
anseio da máquina
1
Como redirecionar e imprimir a saída no console? Parece que "print ()" em Python não pode ser mostrado quando o stdrr é redirecionado?
exteral
70

Você pode redirecionar a impressão com o >>operador.

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

Na maioria dos casos, é melhor escrever o arquivo normalmente.

f.write('whatever')

ou, se você tiver vários itens que deseja escrever com espaços no meio, como print:

f.write(' '.join(('whatever', str(var2), 'etc')))
agf
fonte
2
Se houver muitas instruções de saída, elas podem envelhecer rapidamente. A ideia original dos pôsteres é válida; há algo mais errado com o script.
Gringo Suave
1
A ideia original do pôster é absolutamente inválida. Não há motivo para redirecionar o stdout aqui, pois ele já coloca os dados em uma variável.
máquina anseio
Eu acho que ele quis dizer "tecnicamente válido", na medida em que você pode, de fato, redirecionar sys.stdout, não que seja uma boa idéia.
agf
35

Referência da API Python 2 ou Python 3 :

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

O argumento do arquivo deve ser um objeto com um write(string)método; se não estiver presente ou None, sys.stdoutserá usado. Como os argumentos impressos são convertidos em cadeias de texto, print()não podem ser usados ​​com objetos de arquivo no modo binário. Para estes, use em seu file.write(...)lugar.

Como o objeto de arquivo normalmente contém o write()método, tudo o que você precisa fazer é passar um objeto de arquivo para seu argumento.

Gravar / Substituir no Arquivo

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

Escrever / anexar ao arquivo

with open('file.txt', 'a') as f:
    print('hello world', file=f)
Yeo
fonte
2
Eu apenas confuso porque algumas dessas respostas anteriores eram de monkey patch global sys.stdout:(
Yeo
35

Isso funciona perfeitamente:

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

Agora o hello será gravado no arquivo test.txt. Certifique-se de fechar stdoutcom um close, sem ele o conteúdo não será salvo no arquivo

Pradeep Kumar
fonte
3
mas mesmo se executarmos sys.stdout.close(), se você digitar algo no shell python, ele mostrará erro como ValueError: I/O operation on closed file. imgur.com/a/xby9P . A melhor maneira de lidar com isso é seguir o que @Gringo Suave postou
Mourya
24

Não use print, uselogging

Você pode mudar sys.stdoutpara apontar para um arquivo, mas esta é uma maneira bastante complicada e inflexível de lidar com esse problema. Em vez de usar print, use o loggingmódulo

Com logging, você pode imprimir da mesma maneira que faria stdoutou também pode gravar a saída em um arquivo. Você ainda pode usar os diferentes níveis de mensagens ( critical, error, warning, info, debug) para, por exemplo, só imprimir grandes questões para o console, mas ainda log ações código secundário em um arquivo.

Um exemplo simples

Importe logging, obtenha loggere defina o nível de processamento:

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

Se você deseja imprimir no stdout:

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

Se você também deseja gravar em um arquivo (se você deseja apenas gravar em um arquivo, pule a última seção):

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

Então, onde quer que você use, printuse um dos 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 saber mais sobre o uso de loggingrecursos mais avançados , leia o excelente loggingtutorial nos documentos do Python .

jpyams
fonte
Olá, eu quero usar esse log para gravar os dados do console no arquivo de log com o horário em que esses dados são obtidos. Mas não consigo entender corretamente a função ou a biblioteca de log. Você pode me ajudar com isso
Haris
@haris Leia o tutorial de registro de documentos do Python e confira exemplos em outras perguntas no Stack Overflow (existem muitas). Se você ainda não conseguir fazê-lo funcionar, faça uma nova pergunta.
jpyams
12

A solução mais fácil não é através do python; é através da casca. Desde a primeira linha do seu arquivo ( #!/usr/bin/python), suponho que você esteja em um sistema UNIX. Apenas use printinstruções como normalmente faria e não abra o arquivo no seu script. Quando você executa o arquivo, em vez de

./script.py

para executar o arquivo, use

./script.py > <filename>

onde você substitui <filename>pelo nome do arquivo no qual deseja que a saída seja inserida. O >token informa (a maioria) aos shells para definir stdout para o arquivo descrito pelo token a seguir.

Uma coisa importante que precisa ser mencionada aqui é que "script.py" precisa ser tornado executável para ./script.pyser executado.

Portanto, antes de executar ./script.py, execute este comando

chmod a+x script.py (torne o script executável para todos os usuários)

Aaron Dufour
fonte
3
./script.py> <filename> 2> & 1 Você também precisa capturar o stderr. 2> & 1 farão isso #
rtaft 06/07
1
@rtaft Por quê? A pergunta especificamente deseja canalizar a saída de printpara um arquivo. Seria razoável esperar que stdout (rastreios de pilha e similares) ainda imprima no terminal.
Aaron Dufour
Ele disse que não estava funcionando, o meu também não estava funcionando. Mais tarde, descobri que este aplicativo em que estava trabalhando estava configurado para direcionar tudo para o stderr ... idk por quê.
Rtaft
5

Se você estiver usando Linux, sugiro que você use o teecomando A implementação é assim:

python python_file.py | tee any_file_name.txt

Se você não quiser alterar nada no código, acho que essa pode ser a melhor solução possível. Você também pode implementar o logger, mas precisa fazer algumas alterações no código.

yunus
fonte
1
ótimo; estava procurando
Vicrobot 5/02/19
4

Você pode não gostar desta resposta, mas acho que é a CERTA. Não altere seu destino stdout, a menos que seja absolutamente necessário (talvez você esteja usando uma biblioteca que apenas gera saída para stdout ??? claramente não é o caso aqui).

Eu acho que é um bom hábito que você prepare seus dados com antecedência como uma string, depois abra seu arquivo e escreva tudo ao mesmo tempo. Isso ocorre porque as operações de entrada / saída são maiores quando você tem um identificador de arquivo aberto, maior a probabilidade de ocorrer um erro nesse arquivo (erro de bloqueio de arquivo, erro de E / S etc.). Apenas fazer tudo em uma operação não deixa dúvidas sobre quando pode ter dado errado.

Aqui está um exemplo:

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 então, quando você terminar de coletar suas "linhas de dados" uma linha por item da lista, poderá juntá-las a alguns '\n'caracteres para tornar a saída da tabela completa; talvez até envolva sua instrução de saída em um withbloco, para segurança adicional (fechará automaticamente seu identificador de saída mesmo que algo dê errado):

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

No entanto, se você tiver muitos dados para escrever, poderá escrevê-los um pedaço de cada vez. Não acho relevante para a sua aplicação, mas aqui está a 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()
anseio da máquina
fonte
1
Com o armazenamento em cache do disco, o desempenho do original deve ser aceitável. Essa solução, no entanto, tem a desvantagem de aumentar os requisitos de memória se houver muita saída. Embora provavelmente não tenha nada com que se preocupar aqui, geralmente é uma boa ideia evitar isso, se possível. Mesma idéia usando xrange (intervalo PY3) em vez de gama, etc.
Gringo Suave
@ Gringo: Ele não especificou este requisito. Raramente eu escrevo dados suficientes em um arquivo para que isso seja relevante. Esta não é a mesma idéia que xrange, porque xrange não lida com a E / S do arquivo. O cache do disco pode ajudar, mas ainda é uma prática ruim manter um identificador de arquivo aberto para um grande corpo de código.
máquina anseio
1
Seu comentário se contradiz. Para ser sincero, o aspecto de desempenho de ambas as abordagens é irrelevante para quantidades não enormes de dados. xrange certamente é semelhante, funciona em uma peça de cada vez, em vez de todas de uma vez na memória. Talvez um gerador vs lista seja um exemplo melhor.
Gringo Suave
@ Gringo: Não vejo como meu comentário se contradiz. Talvez o aspecto de desempenho não seja relevante, manter um identificador de arquivo aberto por um longo período sempre aumenta o risco de erro. No arquivo de programação, a E / S é sempre inerentemente mais arriscada do que fazer algo em seu próprio programa, porque significa que você precisa acessar o sistema operacional e mexer nos bloqueios de arquivo. Quanto mais curto você tiver um arquivo aberto, melhor, simplesmente porque você não controla o sistema de arquivos a partir do seu código. O xrange é diferente porque não tem nada a ver com a E / S do arquivo e, para sua informação, também raramente uso o xrange; Cheers
machine yearning
2
@Gringo: Agradeço suas críticas e gostei do debate acalorado. Embora tenhamos discordado em alguns pontos, ainda respeito as suas opiniões, pois fica claro que você tem um bom motivo para se posicionar. Obrigado por terminar razoavelmente e tenha uma boa noite. : P
anseio de máquina
2

Se o redirecionamento stdoutfuncionar para o seu problema, a resposta do Gringo Suave é uma boa demonstração de como fazê-lo.

Para tornar ainda mais fácil , criei uma versão utilizando gerenciadores de contexto para uma sintaxe sucinta de chamada generalizada usando a withinstrução:

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 usá-lo, basta fazer o seguinte (derivado do exemplo de Suave):

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

É útil para redirecionar seletivamente printquando um módulo o usa de uma maneira que você não gosta. A única desvantagem (e esse é o desagradável para muitas situações) é que não funciona se alguém deseja vários encadeamentos com valores diferentes de stdout, mas isso requer um método melhor e mais generalizado: acesso indireto ao módulo. Você pode ver implementações disso em outras respostas a esta pergunta.

Graham
fonte
0

Alterar o valor de sys.stdout altera o destino de todas as chamadas a serem impressas. Se você usar uma maneira alternativa de alterar o destino da impressão, obterá o mesmo resultado.

Seu bug está em outro lugar:

  • pode estar no código que você removeu para a sua pergunta (de onde vem o nome do arquivo para a chamada abrir?)
  • também pode ser que você não esteja aguardando a liberação dos dados: se você imprimir em um terminal, os dados serão liberados após cada nova linha, mas se você imprimir em um arquivo, eles serão liberados apenas quando o buffer stdout estiver cheio (4096 bytes na maioria dos sistemas).
Jerome
fonte
-1

Algo para estender a função de impressão para loops

x = 0
while x <=5:
    x = x + 1
    with open('outputEis.txt', 'a') as f:
        print(x, file=f)
    f.close()
ishiry ish
fonte
não há necessidade de usar whilee não há necessidade de fechar o arquivo ao usar #with
Daniel Stracaboško