Bagaimana cara mengarahkan output 'cetak' ke file menggunakan python?

184

Saya ingin mengarahkan cetak ke file .txt menggunakan python. Saya memiliki loop 'untuk', yang akan 'mencetak' output untuk setiap file .bam saya sementara saya ingin mengarahkan SEMUA output ini ke satu file. Jadi saya mencoba memasukkan

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

di awal skrip saya. Namun saya tidak mendapatkan apa pun di file .txt. Skrip saya adalah:

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

Jadi apa masalahnya? Ada cara lain selain sys.stdout ini?

Saya perlu hasil saya terlihat seperti:

Filename: ERR001268.bam
Readlines finished!
Mean: 233
SD: 10
Interval is: (213, 252)
Lihat ke Dalam
sumber
7
Kenapa tidak digunakan f.write(data)?
Eran Zimmerman Gonen
ya, tapi saya punya beberapa data untuk setiap file bam (mean, SD, interval ...), bagaimana saya bisa meletakkan data ini satu per satu?
LookIntoEast
f.write(line)- Menyisipkan jeda baris di akhir.
Eran Zimmerman Gonen
8
@Eran Zimmerman: f.write(line)tidak menambahkan jeda baris ke data.
hughdbrown
Anda benar, salah saya. f.write(line+'\n')Namun, selalu bisa ..
Eran Zimmerman Gonen

Jawaban:

274

Cara paling jelas untuk melakukan ini adalah mencetak ke objek file:

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

Namun, mengarahkan stdout juga berfungsi untuk saya. Mungkin baik untuk skrip satu kali seperti ini:

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

Mengarahkan secara eksternal dari shell itu sendiri adalah pilihan lain yang baik:

./script.py > out.txt

Pertanyaan Lain:

Apa nama file pertama dalam skrip Anda? Saya tidak melihatnya diinisialisasi.

Dugaan pertama saya adalah bahwa glob tidak menemukan bamfiles, dan karena itu for loop tidak berjalan. Periksa apakah folder tersebut ada, dan cetak bamfiles dalam skrip Anda.

Juga, gunakan os.path.join dan os.path.basename untuk memanipulasi path dan nama file.

Gringo Suave
sumber
Baris 8 dari kode Anda menggunakan variabel bernama nama file, tetapi belum dibuat. Nanti di loop Anda menggunakannya lagi, tetapi tidak relevan.
Gringo Suave
2
Praktek yang buruk untuk mengubah sys.stdout jika Anda tidak perlu.
Kerinduan mesin
3
@ saya, saya tidak yakin itu buruk untuk skrip sederhana seperti ini.
Gringo Suave
4
+1 Haha yah Anda dapat memiliki upvote saya karena ini cara yang tepat untuk melakukannya jika Anda benar-benar harus melakukannya dengan cara yang salah ... Tapi saya masih mengatakan Anda harus melakukannya dengan output file biasa.
Kerinduan mesin
1
Bagaimana cara mengarahkan dan mencetak output pada konsol? Tampaknya "print ()" dalam Python tidak dapat ditampilkan ketika stdrr diarahkan?
exteral
70

Anda dapat mengalihkan cetak dengan >>operator.

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

Dalam kebanyakan kasus, Anda lebih baik menulis ke file secara normal.

f.write('whatever')

atau, jika Anda memiliki beberapa item yang ingin Anda tulis dengan spasi di antaranya, seperti print:

f.write(' '.join(('whatever', str(var2), 'etc')))
agf
sumber
2
Jika ada banyak pernyataan keluaran, ini bisa menjadi cepat cepat. Ide asli poster adalah valid; ada sesuatu yang salah dengan naskahnya.
Gringo Suave
1
Ide asli Poster benar-benar tidak valid. Tidak ada alasan untuk mengarahkan stdout di sini, karena dia sudah mendapatkan data menjadi variabel.
Kerinduan mesin
Saya pikir maksudnya "secara teknis valid", karena Anda dapat, pada kenyataannya, mengarahkan sys.stdout, bukan bahwa itu adalah ide yang baik.
AGF
35

Referensi API Python 2 atau Python 3 :

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

The File argumen harus menjadi objek dengan write(string)metode; jika tidak ada atau None, sys.stdoutakan digunakan. Karena argumen yang dicetak dikonversi ke string teks, print()tidak dapat digunakan dengan objek file mode biner. Untuk ini, gunakan file.write(...)saja.

Karena objek file biasanya berisi write()metode, yang perlu Anda lakukan adalah memasukkan objek file ke dalam argumennya.

Tulis / Timpa ke File

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

Tulis / Tambahkan ke File

with open('file.txt', 'a') as f:
    print('hello world', file=f)
Yeo
sumber
2
Saya hanya bingung mengapa beberapa jawaban sebelumnya adalah untuk menambal monyet global sys.stdout:(
Yeo
35

Ini bekerja dengan sempurna:

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

Sekarang halo akan ditulis ke file test.txt. Pastikan untuk menutup stdoutdengan close, tanpa itu konten tidak akan disimpan dalam file

Pradeep Kumar
sumber
3
tetapi bahkan jika kami melakukan sys.stdout.close(), jika Anda mengetikkan apa pun di shell python itu akan menampilkan kesalahan sebagai ValueError: I/O operation on closed file. imgur.com/a/xby9P . Cara terbaik untuk menangani ini adalah mengikuti apa yang diposting @Gringo Suave
Mourya
24

Jangan gunakan print, gunakanlogging

Anda dapat mengubah sys.stdoutuntuk menunjuk ke file, tetapi ini adalah cara yang cukup kikuk dan tidak fleksibel untuk menangani masalah ini. Alih-alih menggunakan print, gunakan loggingmodul.

Dengan logging, Anda dapat mencetak seperti yang Anda inginkan stdout, atau Anda juga dapat menulis output ke file. Anda bahkan dapat menggunakan tingkat pesan yang berbeda ( critical, error, warning, info, debug), misalnya, hanya mencetak isu utama untuk konsol, tapi masih log tindakan kode kecil ke sebuah file.

Contoh sederhana

Impor logging, dapatkan logger, dan atur tingkat pemrosesan:

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

Jika Anda ingin mencetak ke stdout:

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

Jika Anda ingin juga menulis ke file (jika Anda hanya ingin menulis ke file lewati bagian terakhir):

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

Lalu, di mana pun Anda akan menggunakan printgunakan salah satu loggermetode:

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

Untuk mempelajari lebih lanjut tentang menggunakan loggingfitur yang lebih canggih , baca loggingtutorial yang sangat baik di Python docs .

jpyams
sumber
Hai, Saya ingin menggunakan pencatatan ini untuk menulis data konsol ke file log dengan waktu seperti saat data itu diambil. Tetapi saya tidak dapat memahami fungsi logging atau perpustakaan dengan benar. Bisakah Anda membantu saya dengan ini
haris
@haris Baca tutorial pendataan Python docs dan lihat contoh dalam pertanyaan lain tentang Stack Overflow (ada banyak di antaranya). Jika Anda masih tidak dapat membuatnya berfungsi, ajukan pertanyaan baru.
jpyams
12

Solusi termudah tidak melalui python; itu melalui shell. Dari baris pertama file Anda ( #!/usr/bin/python) Saya kira Anda menggunakan sistem UNIX. Cukup gunakan printpernyataan seperti biasanya, dan jangan buka file sama sekali dalam skrip Anda. Saat Anda menjalankan file, alih-alih

./script.py

untuk menjalankan file, gunakan

./script.py > <filename>

di mana Anda mengganti <filename>dengan nama file yang Anda inginkan untuk output. The >tanda mengatakan (paling) kerang untuk set stdout ke file dijelaskan oleh token berikut.

Satu hal penting yang perlu disebutkan di sini adalah "script.py" harus dapat dieksekusi agar ./script.pydapat dijalankan.

Jadi sebelum menjalankan ./script.py, jalankan perintah ini

chmod a+x script.py (membuat skrip dapat dieksekusi untuk semua pengguna)

Aaron Dufour
sumber
3
./script.py> <filename> 2> & 1 Anda perlu menangkap stderr juga. 2> & 1 akan melakukan itu
rtaft
1
@ Art Mengapa? Pertanyaannya secara khusus ingin mengirim output printke file. Akan masuk akal untuk mengharapkan stdout (jejak tumpukan dan sejenisnya) untuk tetap mencetak ke terminal.
Aaron Dufour
Dia mengatakan itu tidak berfungsi, milikku juga tidak berfungsi. Saya kemudian menemukan bahwa aplikasi yang sedang saya kerjakan ini dikonfigurasi untuk mengarahkan semuanya ke stderr ... idk why.
rtaft
5

Jika Anda menggunakan Linux, saya sarankan Anda untuk menggunakan teeperintah. Implementasinya seperti ini:

python python_file.py | tee any_file_name.txt

Jika Anda tidak ingin mengubah apa pun dalam kode, saya pikir ini mungkin solusi terbaik. Anda juga dapat mengimplementasikan logger tetapi Anda perlu melakukan beberapa perubahan pada kode.

Yunus
sumber
1
Bagus; sedang mencarinya
Vicrobot
4

Anda mungkin tidak menyukai jawaban ini, tapi saya pikir itu BENAR. Jangan mengubah tujuan stdout Anda kecuali jika benar-benar diperlukan (mungkin Anda menggunakan perpustakaan yang hanya menghasilkan stdout ??? jelas tidak terjadi di sini).

Saya pikir sebagai kebiasaan yang baik Anda harus menyiapkan data Anda sebelumnya sebagai sebuah string, kemudian buka file Anda dan tulis semuanya sekaligus. Ini karena operasi input / output semakin lama Anda membuka file handle, semakin besar kemungkinan terjadi kesalahan pada file ini (kesalahan kunci file, kesalahan i / o, dll). Hanya melakukan semuanya dalam satu operasi tidak meninggalkan pertanyaan untuk ketika itu mungkin salah.

Ini sebuah contoh:

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

Dan kemudian ketika Anda sudah selesai mengumpulkan "garis data" Anda satu baris per item daftar, Anda dapat bergabung dengan mereka dengan beberapa '\n'karakter untuk membuat semuanya menjadi tabel output; mungkin bahkan membungkus pernyataan output Anda dalam sebuah withblok, untuk keamanan tambahan (secara otomatis akan menutup gagang output Anda bahkan jika ada yang salah):

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

Namun jika Anda memiliki banyak data untuk ditulis, Anda dapat menulisnya satu per satu. Saya tidak berpikir itu relevan dengan aplikasi Anda tetapi inilah alternatifnya:

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()
kerinduan mesin
sumber
1
Dengan kinerja caching disk yang asli harus dapat diterima. Namun solusi ini memiliki kelemahan memenuhi kebutuhan memori jika ada banyak output. Meskipun mungkin tidak ada yang perlu dikhawatirkan di sini, umumnya adalah ide yang baik untuk menghindari ini jika memungkinkan. Gagasan yang sama dengan menggunakan xrange (rentang py3) alih-alih rentang, dll.
Gringo Suave
@Gringo: Dia tidak menentukan persyaratan ini. Jarang saya pernah menulis data yang cukup ke file yang ini akan relevan. Ini bukan ide yang sama dengan xrange karena xrange tidak berurusan dengan file i / o. Disk caching mungkin bisa membantu, tetapi itu masih merupakan praktik yang buruk untuk menjaga file menangani terbuka untuk sejumlah besar kode.
Kerinduan mesin
1
Komentar Anda bertentangan dengan dirinya sendiri. Sejujurnya aspek kinerja kedua pendekatan ini tidak relevan untuk jumlah data yang tidak besar. xrange tentu saja mirip, ia bekerja pada satu bagian pada satu waktu dan bukannya sekaligus dalam memori. Mungkin daftar generator vs adalah contoh yang lebih baik.
Gringo Suave
@Gringo: Saya gagal melihat bagaimana komentar saya bertentangan dengan dirinya sendiri. Mungkin aspek kinerja tidak relevan, menjaga pegangan file tetap terbuka untuk waktu yang lama selalu meningkatkan risiko kesalahan. Dalam pemrograman file i / o selalu secara inheren lebih berisiko daripada melakukan sesuatu dalam program Anda sendiri, karena itu berarti Anda harus menjangkau melalui OS dan main-main dengan kunci file. Semakin pendek Anda membuka file, semakin baik, hanya karena Anda tidak mengontrol sistem file dari kode Anda. xrange berbeda karena tidak ada hubungannya dengan file i / o, dan FYI saya jarang menggunakan xrange juga; sorakan
kerinduan mesin
2
@Gringo: Saya menghargai kritik Anda dan menikmati debat panas. Meskipun kami tidak setuju pada beberapa hal, saya masih menghormati pandangan Anda karena jelas Anda memiliki alasan untuk mengambil sikap. Terima kasih telah mengakhirinya dengan wajar dan selamat malam. : P
kerinduan mesin
2

Jika pengalihan stdoutberfungsi untuk masalah Anda, jawaban Gringo Suave adalah demonstrasi yang baik untuk melakukannya.

Untuk membuatnya lebih mudah , saya membuat versi menggunakan contextmanager untuk sintaks panggilan singkat yang digeneralisasikan menggunakan withpernyataan:

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

Untuk menggunakannya, Anda cukup melakukan hal berikut (berasal dari contoh Suave):

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

Ini berguna untuk mengarahkan secara selektif printketika sebuah modul menggunakannya dengan cara yang tidak Anda sukai. Satu-satunya kelemahan (dan ini adalah dealbreaker untuk banyak situasi) adalah tidak bekerja jika seseorang menginginkan banyak utas dengan nilai berbeda stdout, tetapi itu membutuhkan metode yang lebih baik dan lebih umum: akses modul tidak langsung. Anda dapat melihat implementasi itu dalam jawaban lain untuk pertanyaan ini.

Graham
sumber
0

Mengubah nilai sys.stdout tidak mengubah tujuan semua panggilan untuk dicetak. Jika Anda menggunakan cara alternatif untuk mengubah tujuan cetak, Anda akan mendapatkan hasil yang sama.

Bug Anda ada di tempat lain:

  • itu bisa dalam kode yang Anda hapus untuk pertanyaan Anda (dari mana nama file berasal dari panggilan untuk membuka?)
  • bisa juga Anda tidak menunggu data untuk memerah: jika Anda mencetak pada terminal, data memerah setelah setiap baris baru, tetapi jika Anda mencetak ke file, itu hanya memerah ketika buffer stdout penuh (4096 byte) pada kebanyakan sistem).
Jerome
sumber
-1

Sesuatu untuk memperluas fungsi cetak untuk loop

x = 0
while x <=5:
    x = x + 1
    with open('outputEis.txt', 'a') as f:
        print(x, file=f)
    f.close()
ishiry ish
sumber
tidak perlu digunakan whiledan tidak perlu menutup file saat menggunakanwith
Daniel Stracaboško