Як перенаправити висновок '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)

7
Чому б не використовувати f.write(data)?
Eran Zimmerman Gonen

так, але у мене є декілька даних для кожного файлу бама (середній, SD, інтервал ...), як я можу ці дані покласти один за одним?
LookIntoEast

f.write(line)- він вставляє розрив рядка в кінці.
Eran Zimmerman Gonen

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

Інші питання:

Яке перше ім’я файлу у вашому сценарії? Я не бачу його ініціалізованим.

Моя перша здогадка полягає в тому, що глобус не знаходить жодної бамфили, і тому цикл for не працює. Перевірте, чи існує папка, і роздрукуйте bamfiles у вашому сценарії.

Також використовуйте os.path.join та os.path.basename для маніпулювання шляхами та іменами.


У рядку 8 вашого коду використовується змінна назва файлу, але вона ще не створена. Пізніше в циклі ви його знову використовуєте, але не актуально.
Gringo Suave

2
Погана практика змінити sys.stdout, якщо цього не потрібно.
машина, яка тужить

3
@my Я не переконаний, що це погано для простого сценарію, як це.
Gringo Suave

4
+1 Ха-ха, ви можете отримати мою нагороду, тому що це правильний спосіб зробити це, якщо ви абсолютно повинні зробити це неправильно ... Але я все одно кажу, що ви повинні робити це з регулярним виведенням файлів.
машина, яка тужить

1
Як перенаправити та роздрукувати вихід на консолі? Здається, що "print ()" в Python не може бути показаний, коли stdrr буде переспрямовано?
продовження

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

2
Якщо є багато заявок на вихід, вони можуть швидко старіти. Оригінальна ідея плакатів є дійсною; в сценарії щось інше не так.
Gringo Suave

1
Оригінальна ідея плаката абсолютно недійсна. Тут немає причин перенаправляти stdout, оскільки він вже отримує дані в змінну.
машина, яка тужить

Я думаю, що він мав на увазі "технічно обґрунтований", оскільки ви насправді можете перенаправляти 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. Переконайтесь, що закрийте " stdouta" close, без цього вміст не буде збережено у файлі


3
але навіть якщо ми виконаємо sys.stdout.close(), якщо ви введете що-небудь у оболонці python, це покаже помилку як ValueError: I/O operation on closed file. 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

Якщо ви хочете надрукувати stdout:

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 .


Привіт, я хочу використовувати цей журнал для запису даних консолі у файл журналу з тим часом, у який час приймаються ці дані. Але я не в змозі правильно зрозуміти функцію журналу або бібліотеку. Чи можете ви мені в цьому допомогти
харіс

@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> <ім'я файла> 2> & 1 Вам також потрібно захопити stderr. 2> & 1 зробимо це
rtaft

1
@rtaft Чому? Питання спеціально хоче передавати вихід у printфайл. Було б розумно очікувати, що stdout (сліди стека тощо) все-таки надрукує термінал.
Аарон Дюфур

Він сказав, що це не працює, і моє теж не працює. Пізніше я виявив, що цей додаток, над яким я працюю, був налаштований спрямовувати все на 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 діапазон) замість діапазону тощо.
Gringo Suave

@Gringo: Він не вказав цю вимогу. Рідко я коли-небудь записую у файл достатньо даних, щоб це було актуально. Це не та сама ідея, як xrange, тому що xrange не має справу з введенням файлів. Кешування диска може допомогти, але все-таки погана практика зберігати ручку файлу відкритою для великого коду коду.
машина, яка тужить

1
Ваш коментар суперечить собі. Якщо чесно, аспект продуктивності обох підходів не має великого значення для величезних обсягів даних. xrange, безумовно, схожий, він працює по одній штуці за раз, а не всі відразу в пам'яті. Можливо, генератор проти списку є кращим прикладом.
Gringo Suave

@Gringo: Я не бачу, як мій коментар суперечить собі. Можливо, аспект продуктивності не є актуальним, якщо тримати час відкритою ручкою файлу завжди збільшує ризик помилки. У програмуванні файлів введення-виведення завжди є більш ризикованим, ніж робити щось у власній програмі, тому що це означає, що вам потрібно дотягнутися до операційної системи і заплутатися з блокуванням файлів. Чим коротше у вас відкритий файл, тим краще, просто тому, що ви не керуєте файловою системою зі свого коду. xrange відрізняється тим, що він не має нічого спільного з введенням / виводом файлів, і FYI я рідко використовую xrange; ура
машинне тяжіння

2
@Gringo: Я ціную вашу критику і насолоджувався бурхливою дискусією. Незважаючи на те, що ми не погоджувались з деяких питань, я все-таки поважаю ваші погляди, оскільки зрозуміло, що у вас є вагомі підстави висловити свою позицію. Дякуємо, що закінчили це розумно і дуже спокійно прожили. : P
машина, яка тужить

2

Якщо перенаправлення stdoutпрацює на вашу проблему, відповідь Грінго Суаве - це хороша демонстрація того, як це зробити.

Щоб зробити це ще простіше , я створив версію, яка використовує менеджери контексту для стислого узагальненого синтаксису виклику за допомогою 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 змінює призначення всіх дзвінків для друку. Якщо ви використовуєте альтернативний спосіб зміни призначення друку, ви отримаєте той же результат.

Ваша помилка десь інше:

  • це може бути код, який ви видалили для свого запитання (звідки береться ім'я файлу для відкриття дзвінка?)
  • також може бути, що ви не чекаєте, коли дані будуть видалені: якщо ви друкуєте на терміналі, дані видаляються після кожного нового рядка, але якщо ви друкуєте у файл, він видається лише тоді, коли буфер 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
Даніель Стракабошко
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.