Как перенаправить вывод 'print' в файл, используя python?

184

Я хочу перенаправить печать в файл .txt с помощью Python. У меня есть цикл for, который будет «печатать» выходные данные для каждого из моих файлов .bam, в то время как я хочу перенаправить ВСЕ эти выходные данные в один файл. Вот я и попытался поставить

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

в начале моего сценария. Однако я ничего не получаю в файле .txt. Мой сценарий:

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

Так в чем проблема? Любой другой способ, кроме этого sys.stdout?

Мне нужно, чтобы мой результат выглядел так:

Filename: ERR001268.bam
Readlines finished!
Mean: 233
SD: 10
Interval is: (213, 252)
LookIntoEast
источник
7
Почему бы не использовать f.write(data)?
Эран Циммерман Гонен
да, но у меня есть несколько данных для каждого файла BAM (среднее значение, SD, интервал ...), как я могу поместить эти данные по одному?
LookIntoEast
f.write(line)- он вставляет разрыв строки в конце.
Эран Циммерман Гонен
8
@Eran Zimmerman: f.write(line)не добавляет разрыв данных в данные.
hughdbrown
Ты прав, мой плохой. Однако всегда мог f.write(line+'\n')..
Эран Циммерман Гонен

Ответы:

274

Наиболее очевидный способ сделать это - напечатать объект файла:

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

Тем не менее, перенаправление stdout также работает для меня. Это, вероятно, хорошо для одноразового сценария, такого как этот:

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

Перенаправление извне из самой оболочки является еще одним хорошим вариантом:

./script.py > out.txt

Другие вопросы:

Какое первое имя файла в вашем сценарии? Я не вижу его инициализированным.

Мое первое предположение состоит в том, что glob не находит никаких bamfiles, и поэтому цикл for не запускается. Убедитесь, что папка существует, и распечатайте bamfiles в вашем скрипте.

Также используйте os.path.join и os.path.basename для манипулирования путями и именами файлов.

Гринго Суаве
источник
В строке 8 вашего кода используется переменная с именем filename, но она еще не создана. Позже в цикле вы используете его снова, но не актуально.
Гринго Суаве
2
Плохая практика менять sys.stdout, если вам это не нужно.
машина тоскует
3
@ Я не уверен, что это плохо для такого простого сценария.
Гринго Суаве
4
+1 Хаха, вы можете получить мое возражение, потому что это правильный способ сделать это, если вы абсолютно должны сделать это неправильно ... Но я все же говорю, что вы должны делать это с обычным выводом файла.
машина тоскует
1
Как перенаправить и распечатать вывод на консоль? Кажется, "print ()" в Python не может быть показано, когда stdrr перенаправлен?
Exteral
70

Вы можете перенаправить печать с >>оператором.

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

В большинстве случаев вам лучше просто записать в файл нормально.

f.write('whatever')

или, если у вас есть несколько элементов, которые вы хотите написать с пробелами между ними, например print:

f.write(' '.join(('whatever', str(var2), 'etc')))
AGF
источник
2
Если выходных операторов много, они могут быстро устареть. Оригинальная идея постеров действительна; в скрипте что-то не так
Гринго Суаве
1
Оригинальная идея плаката абсолютно неверна. Здесь нет причин перенаправлять стандартный вывод, так как он уже получает данные в переменную.
машина тоскует
Я думаю, что он имел в виду «технически обоснованный», в том смысле, что вы можете, на самом деле, перенаправить sys.stdout, но это не было хорошей идеей.
agf
35

Справочник по API Python 2 или Python 3 :

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

Файла аргумент должен быть объектом с write(string)методом; если его нет или None, sys.stdoutбудет использоваться. Поскольку напечатанные аргументы преобразуются в текстовые строки, print()их нельзя использовать с объектами файлов в двоичном режиме. Для этого используйте file.write(...)вместо этого.

Поскольку файловый объект обычно содержит write()метод, все, что вам нужно сделать, это передать объект файла в его аргумент.

Запись / перезапись в файл

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

Написать / Добавить в файл

with open('file.txt', 'a') as f:
    print('hello world', file=f)
Ео
источник
2
Я просто запутался, почему некоторые из этих более ранних ответов заключались в том, чтобы обезьяна исправить глобально sys.stdout:(
Yeo
35

Это прекрасно работает:

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

Теперь привет будет записан в файл test.txt. Убедитесь, что закрыли stdoutс close, без него содержимое не будет сохранено в файл

Прадип Кумар
источник
3
но даже если мы выполним sys.stdout.close(), если вы ValueError: I/O operation on closed file. введете что-либо в оболочку python, это будет отображать ошибку как imgur.com/a/xby9P . Лучший способ справиться с этим - следовать тому, что написал @Gringo Suave
Mourya
24

Не используйте print, используйтеlogging

Вы можете изменить sys.stdoutуказатель на файл, но это довольно неуклюжий и негибкий способ решения этой проблемы. Вместо использования printиспользуйте loggingмодуль.

С помощью loggingвы можете печатать так же, как вы stdout, или вы также можете записать вывод в файл. Вы даже можете использовать различные уровни сообщений ( critical, error, warning, info, debug), например, печатать только основные вопросы , на консоль, но все - таки войти незначительную коду действие в файл.

Простой пример

Импортируйте logging, получите loggerи установите уровень обработки:

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

Если вы хотите распечатать на стандартный вывод:

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

Если вы также хотите записать в файл (если вы хотите записать только в файл, пропустите последний раздел):

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

Затем, где бы вы ни использовали, printиспользуйте один из loggerметодов:

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

Чтобы узнать больше об использовании более сложных loggingфункций, прочитайте превосходное loggingруководство в документации по Python .

jpyams
источник
Привет, я хочу использовать эту запись для записи данных консоли в файл журнала с указанием времени, когда эти данные были получены. Но я не могу понять функцию регистрации или библиотеки должным образом. Можете ли вы помочь мне с этим
Харис
@haris Прочтите руководство по ведению журнала документов Python и ознакомьтесь с примерами из других вопросов о переполнении стека (их много). Если вы все еще не можете заставить его работать, задайте новый вопрос.
Jpyams
12

Самое простое решение не через Python; его через оболочку. Из первой строки вашего файла ( #!/usr/bin/python) я предполагаю, что вы работаете в системе UNIX. Просто используйте printоператоры, как обычно, и вообще не открывайте файл в вашем скрипте. Когда вы идете, чтобы запустить файл, а не

./script.py

чтобы запустить файл, используйте

./script.py > <filename>

где вы заменяете <filename>на имя файла, в который хотите включить вывод. >Маркер говорит (большинство) оболочки , чтобы установить стандартный вывод в файл , описываемый следующим токен.

Здесь важно упомянуть одну важную вещь: для запуска «script.py» необходимо сделать исполняемым ./script.py.

Поэтому перед запуском ./script.pyвыполните эту команду

chmod a+x script.py (сделать сценарий исполняемым для всех пользователей)

Аарон Дюфур
источник
3
./script.py> <filename> 2> & 1 Вам также нужно перехватить stderr. 2> & 1 сделает это
rtaft
1
@rtaft Почему? Вопрос, в частности, хочет направить вывод printв файл. Было бы разумно ожидать, что стандартный вывод (следы стека и тому подобное) все еще будет печататься в терминал.
Аарон Дюфур
Он сказал, что это не работает, у меня тоже не работает. Позже я обнаружил, что это приложение, над которым я работаю, было сконфигурировано так, чтобы направлять все в stderr ... idk, почему.
rtaft
5

Если вы используете Linux, я предлагаю вам использовать teeкоманду. Реализация идет так:

python python_file.py | tee any_file_name.txt

Если вы не хотите ничего менять в коде, я думаю, что это может быть лучшим решением. Вы также можете реализовать регистратор, но вам нужно внести некоторые изменения в код.

Юнуса
источник
1
здорово; искал это
Vicrobot
4

Вам может не понравиться этот ответ, но я думаю, что он правильный. Не меняйте место назначения stdout, если это не является абсолютно необходимым (может быть, вы используете библиотеку, которая выводит только в stdout - здесь явно не так).

Я считаю хорошей привычкой заранее подготовить данные в виде строки, а затем открыть файл и написать все сразу. Это связано с тем, что чем дольше выполняются операции ввода / вывода, тем больше вероятность возникновения ошибки в этом файле (ошибка блокировки файла, ошибка ввода-вывода и т. Д.). Простое выполнение всего за одну операцию не оставляет вопроса о том, когда это могло пойти не так.

Вот пример:

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

И затем, когда вы все закончите, собирая свои «строки данных» по одной строке на элемент списка, вы можете объединить их с некоторыми '\n'символами, чтобы сделать все это выводимым; может быть, даже обернуть ваш оператор вывода в withблок, для дополнительной безопасности (автоматически закроет ваш дескриптор вывода, даже если что-то пойдет не так):

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

Однако, если у вас есть много данных для записи, вы можете написать их по одному фрагменту за раз. Я не думаю, что это имеет отношение к вашему заявлению, но вот альтернатива:

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
При дисковом кешировании производительность оригинала должна быть приемлемой. Это решение, однако, имеет недостаток, заключающийся в увеличении требований к памяти, если было много выходных данных. Хотя, вероятно, здесь не о чем беспокоиться, как правило, лучше избегать этого, если это возможно. Та же идея, что и использование xrange (py3 range) вместо range и т. Д.
Gringo Suave
@Gringo: он не уточнил это требование. Редко я когда-либо записываю достаточно данных в файл, чтобы это было актуально. Это не та же идея, что и у xrange, потому что xrange не работает с файловым вводом / выводом. Кэширование диска может помочь, но по-прежнему плохая практика держать дескриптор файла открытым для большого объема кода.
машина тоскует
1
Ваш комментарий противоречит сам себе. Честно говоря, аспект производительности обоих подходов не имеет значения для огромных объемов данных. xrange, конечно, похож, он работает по одному фрагменту за раз, а не все сразу в памяти. Возможно, генератор против списка - лучший пример.
Гринго Суаве
@Gringo: я не вижу, как мой комментарий противоречит самому себе. Может быть, аспект производительности не имеет значения, сохраняя дескриптор файла открытым в течение длительного периода, всегда увеличивает риск ошибки. В программировании файлов ввод / вывод всегда более рискован, чем выполнение каких-либо действий в вашей собственной программе, потому что это означает, что вы должны обращаться к ОС и возиться с блокировками файлов. Чем короче у вас открытый файл, тем лучше, потому что вы не управляете файловой системой из своего кода. xrange отличается тем, что не имеет ничего общего с файловым вводом-выводом, и, к вашему сведению, я редко использую xrange; ура
машина тоскует
2
@Gringo: Я ценю вашу критику и наслаждаюсь жаркими дебатами. Несмотря на то, что мы не согласились по некоторым пунктам, я все же уважаю ваши взгляды, поскольку ясно, что у вас есть веские основания для того, чтобы занять свою позицию. Спасибо за разумное завершение и очень спокойной ночи. : P
машина тоскует
2

Если перенаправление stdoutработает для вашей проблемы, ответ Gringo Suave является хорошей демонстрацией того, как это сделать.

Чтобы сделать это еще проще , я сделал версию, использующую контекстные менеджеры для краткого обобщенного синтаксиса вызова, используя withоператор:

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

Чтобы использовать его, вы просто делаете следующее (полученный из примера Suave):

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

Это полезно для выборочного перенаправления, printкогда модуль использует его так, как вам не нравится. Единственный недостаток (а во многих ситуациях это является нарушителем правил) заключается в том, что он не работает, если требуется несколько потоков с разными значениями stdout, но для этого требуется лучший, более обобщенный метод: косвенный доступ к модулю. Вы можете увидеть реализацию этого в других ответах на этот вопрос.

Грэхем
источник
0

Изменение значения sys.stdout меняет назначение всех вызовов для печати. Если вы используете альтернативный способ изменить место назначения печати, вы получите тот же результат.

Ваша ошибка в другом месте:

  • это может быть в коде, который вы удалили для своего вопроса (откуда берется имя файла для вызова open?)
  • может также случиться так, что вы не ожидаете сброса данных: если вы печатаете на терминале, данные сбрасываются после каждой новой строки, но если вы печатаете в файл, он сбрасывается только тогда, когда буфер stdout заполнен (4096 байт на большинстве систем).
Джером
источник
-1

Что-то, чтобы расширить функцию печати для петель

x = 0
while x <=5:
    x = x + 1
    with open('outputEis.txt', 'a') as f:
        print(x, file=f)
    f.close()
Ишири Иш
источник
не нужно использовать whileи не нужно закрывать файл при использованииwith
Даниэль Стракабошко