วิธีเปลี่ยนเส้นทาง 'พิมพ์' เอาต์พุตไปยังไฟล์โดยใช้ 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

ใช่ แต่ฉันมีข้อมูลหลายไฟล์สำหรับแต่ละไฟล์ bam (หมายถึง SD ช่วงเวลา ... ) ฉันจะใส่ข้อมูลเหล่านี้ทีละไฟล์ได้อย่างไร
LookIntoEast

f.write(line)- มันแทรกตัวแบ่งบรรทัดในตอนท้าย
Eran Zimmerman Gonen

8
@Eran Zimmerman: f.write(line)ไม่เพิ่มตัวแบ่งบรรทัดลงในข้อมูล
hughdbrown

คุณพูดถูกฉันไม่ดี f.write(line+'\n')อย่างไรก็ตามสามารถทำได้เสมอ..
Eran Zimmerman Gonen

คำตอบ:


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 for loop ไม่ทำงาน ตรวจสอบว่าโฟลเดอร์นั้นมีอยู่และพิมพ์ bamfiles ในสคริปต์ของคุณ

นอกจากนี้ให้ใช้os.path.join และ os.path.basenameเพื่อจัดการพา ธ และชื่อไฟล์


บรรทัดที่ 8 ของรหัสของคุณใช้ตัวแปรชื่อไฟล์ แต่ยังไม่ได้สร้าง ต่อมาในวงคุณใช้อีกครั้ง แต่ไม่เกี่ยวข้อง
Gringo Suave

2
แนวทางปฏิบัติที่ไม่เหมาะสมในการเปลี่ยน sys.stdout หากคุณไม่ต้องการ
เครื่องจักรโหยหา

3
@ ฉันฉันไม่เชื่อว่ามันจะไม่ดีสำหรับสคริปต์แบบนี้
Gringo Suave

4
+1 ฮ่าฮ่าคุณสามารถมี upvote ของฉันเพราะมันเป็นวิธีที่ถูกต้องที่จะทำถ้าคุณต้องทำมันอย่างผิดวิธี ...
เครื่องจักรโหยหา

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

Python 2หรือPython 3 API อ้างอิง:

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()ถ้าคุณพิมพ์อะไรในเปลือกหลามก็จะแสดงข้อผิดพลาดเป็นimgur.com/a/xby9PValueError: I/O operation on closed file. วิธีที่ดีที่สุดในการจัดการสิ่งนี้คือทำตามสิ่งที่ @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


สวัสดีฉันต้องการใช้บันทึกนี้เพื่อเขียนข้อมูลคอนโซลไปยังไฟล์บันทึกพร้อมเวลาตามเวลาที่ใช้ข้อมูล แต่ฉันไม่สามารถเข้าใจฟังก์ชันการบันทึกหรือไลบรารีได้อย่างถูกต้อง คุณช่วยฉันได้ไหม
haris

@haris อ่านบทช่วยสอนการบันทึกการทำงานของ Pythonและดูตัวอย่างในคำถามอื่น ๆ เกี่ยวกับ Stack Overflow (มีจำนวนมาก) หากคุณยังไม่สามารถใช้งานได้ให้ถามคำถามใหม่
jpyams

12

ทางออกที่ง่ายที่สุดไม่ได้ผ่านงูหลาม มันผ่านเปลือก จากบรรทัดแรกของไฟล์ของคุณ ( #!/usr/bin/python) ฉันเดาว่าคุณอยู่ในระบบ UNIX เพียงแค่ใช้printคำสั่งตามปกติและอย่าเปิดไฟล์เลยในสคริปต์ของคุณ เมื่อคุณไปเรียกใช้ไฟล์แทน

./script.py

เพื่อเรียกใช้ไฟล์ให้ใช้

./script.py > <filename>

โดยที่คุณแทนที่<filename>ด้วยชื่อไฟล์ที่คุณต้องการให้เอาต์พุตเข้าไป >โทเค็นบอก (ส่วนใหญ่) หอยชุด stdout ไปยังแฟ้มอธิบายโดยโทเค็นดังต่อไปนี้

สิ่งสำคัญประการหนึ่งที่ต้องกล่าวถึงในที่นี้คือ "script.py" จะต้อง./script.pyสามารถเรียกใช้เพื่อให้ทำงานได้

ดังนั้นก่อนรัน./script.pyให้รันคำสั่งนี้

chmod a+x script.py (ทำให้สคริปต์เรียกใช้งานได้สำหรับผู้ใช้ทั้งหมด)


3
./script.py> <filename> 2> & 1 คุณต้องจับ stderr ด้วย 2> & 1 จะทำเช่นนั้น
rtaft

1
@rtaft ทำไม คำถามนี้ต้องการส่งออกprintไปยังไฟล์โดยเฉพาะ มันจะสมเหตุสมผลที่จะคาดหวัง stdout (ร่องรอยสแต็กและชอบ) ยังคงพิมพ์ไปยังสถานี
Aaron Dufour

เขาบอกว่ามันไม่ทำงานฉันก็ไม่ทำงานเหมือนกัน ฉันค้นพบในภายหลังว่าแอพที่ฉันกำลังทำงานอยู่นี้ได้รับการกำหนดค่าให้นำทุกอย่างไปยัง stderr ... idk ทำไม
rtaft

5

หากคุณใช้ Linux ฉันแนะนำให้คุณใช้teeคำสั่ง การนำไปใช้จะเป็นดังนี้:

python python_file.py | tee any_file_name.txt

หากคุณไม่ต้องการเปลี่ยนแปลงอะไรในรหัสฉันคิดว่านี่อาจเป็นทางออกที่ดีที่สุด นอกจากนี้คุณยังสามารถใช้ตัวบันทึก แต่คุณต้องทำการเปลี่ยนแปลงบางอย่างในรหัส


1
ที่ดี; กำลังมองหามัน
Vicrobot

4

คุณอาจไม่ชอบคำตอบนี้ แต่ฉันคิดว่ามันเหมาะสม อย่าเปลี่ยนปลายทาง stdout ของคุณเว้นแต่จะจำเป็นจริงๆ (บางทีคุณอาจใช้ห้องสมุดที่ส่งออกไปยัง stdout เท่านั้นไม่ใช่ชัดเจนในกรณีนี้)

ฉันคิดว่าเป็นนิสัยที่ดีคุณควรเตรียมข้อมูลของคุณล่วงหน้าเป็นสตริงจากนั้นเปิดไฟล์ของคุณและเขียนสิ่งทั้งหมดในครั้งเดียว นี่เป็นเพราะการดำเนินการอินพุต / เอาท์พุตเป็นเวลานานกว่าที่คุณเปิดการจัดการไฟล์โอกาสที่จะเกิดข้อผิดพลาดมากขึ้นกับไฟล์นี้ (ข้อผิดพลาดการล็อกไฟล์ข้อผิดพลาด i / o ฯลฯ ) เพียงแค่ทำทุกอย่างในการดำเนินการครั้งเดียวก็ไม่มีคำถามว่าเมื่อใดที่มันอาจจะผิดพลาด

นี่คือตัวอย่าง:

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 ไม่ได้จัดการกับไฟล์ i / o การแคชดิสก์อาจช่วยได้ แต่ก็ยังเป็นวิธีปฏิบัติที่ไม่ดีที่จะเปิดการจัดการไฟล์ไว้สำหรับโค้ดขนาดใหญ่
เครื่องโหยหา

1
ความคิดเห็นของคุณขัดแย้งกับตัวเอง ความซื่อสัตย์ต่อประสิทธิภาพการทำงานของทั้งสองวิธีนั้นไม่เกี่ยวข้องกับข้อมูลจำนวนมาก xrange แน่นอนว่าคล้ายกันมันทำงานได้ทีละชิ้นแทนที่จะเป็นครั้งเดียวในหน่วยความจำ บางทีรายการตัวสร้าง vs เป็นตัวอย่างที่ดีกว่า
Gringo Suave

@Gringo: ฉันไม่เห็นว่าความคิดเห็นของฉันขัดแย้งกับตัวเองอย่างไร บางทีประสิทธิภาพการทำงานอาจไม่เกี่ยวข้องกันการเปิดตัวจัดการไฟล์ไว้เป็นระยะเวลานานอาจทำให้เกิดข้อผิดพลาดได้ ในการเขียนโปรแกรมไฟล์ i / o มักจะมีความเสี่ยงมากกว่าการทำบางอย่างภายในโปรแกรมของคุณเองเพราะมันหมายความว่าคุณต้องเข้าถึงผ่านระบบปฏิบัติการและยุ่งเกี่ยวกับการล็อคไฟล์ ยิ่งคุณเปิดไฟล์สั้นเท่าไหร่ก็ยิ่งดีเพราะคุณไม่ได้ควบคุมระบบไฟล์จากรหัสของคุณ xrange แตกต่างกันเนื่องจากไม่มีส่วนเกี่ยวข้องกับไฟล์ i / o และ FYI ฉันไม่ค่อยใช้ xrange เช่นกัน ไชโย
machine โหยหา

2
@Gringo: ฉันขอขอบคุณคำวิจารณ์ของคุณและสนุกกับการอภิปรายที่ร้อนแรง แม้ว่าเราจะไม่เห็นด้วยกับบางประเด็นก็ตามฉันยังคงเคารพมุมมองของคุณเนื่องจากเป็นที่ชัดเจนว่าคุณมีเหตุผลที่ดีในการทำท่าทางของคุณ ขอบคุณสำหรับการจบที่สมเหตุสมผลและมีคืนที่ดีมาก : P
machine โหยหา

2

หากการเปลี่ยนเส้นทางใช้stdoutงานได้สำหรับปัญหาของคุณคำตอบของ Gringo Suaveคือการสาธิตที่ดีสำหรับวิธีการทำ

เพื่อให้ง่ายยิ่งขึ้นฉันได้สร้างเวอร์ชันที่ใช้contextmanagersสำหรับไวยากรณ์การโทรทั่วไปที่รวบรัดโดยใช้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
Daniel Stracaboško
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.