เขียน Python stdout เป็นไฟล์ทันที


51

เมื่อพยายามเขียน stdout จากสคริปต์ Python ไปยังไฟล์ข้อความ ( python script.py > log) ไฟล์ข้อความจะถูกสร้างขึ้นเมื่อคำสั่งเริ่มทำงาน แต่เนื้อหาจริงจะไม่ถูกเขียนจนกว่าสคริปต์ Python จะเสร็จสิ้น ตัวอย่างเช่น:

script.py:

import time
for i in range(10):
    print('bla')
    time.sleep(5)

พิมพ์ไปยัง stdout ทุก 5 วินาทีเมื่อเรียกด้วยpython script.pyแต่เมื่อฉันโทรpython script.py > logขนาดของไฟล์บันทึกจะเป็นศูนย์จนกระทั่งสคริปต์เสร็จสิ้น เป็นไปได้หรือไม่ที่จะเขียนลงในไฟล์บันทึกโดยตรงเช่นคุณสามารถติดตามความคืบหน้าของสคริปต์ (เช่นใช้tail)?

แก้ไขมันปรากฎว่าpython -u script.pyเป็นการหลอกลวงฉันไม่รู้เกี่ยวกับบัฟเฟอร์ของ stdout


1
@jezmck ฉันอาจเข้าใจคำถามผิดไป
zyxue

คำตอบ:


64

สิ่งนี้เกิดขึ้นเพราะปกติเมื่อกระบวนการ STDOUT ถูกเปลี่ยนเส้นทางไปยังสิ่งอื่นนอกเหนือจากเทอร์มินัลเอาต์พุตจะถูกบัฟเฟอร์ลงในบัฟเฟอร์เฉพาะขนาด OS บางระบบ (อาจเป็น 4k หรือ 8k ในหลายกรณี) ในทางกลับกันเมื่อส่งออกไปยังเทอร์มินัล STDOUT จะถูกบัฟเฟอร์บรรทัดหรือไม่ถูกบัฟเฟอร์เลยดังนั้นคุณจะเห็นผลลัพธ์หลังจาก\nแต่ละตัวอักษรหรือแต่ละตัวอักษร

โดยทั่วไปคุณสามารถเปลี่ยนบัฟเฟอร์ STDOUT ด้วยstdbufยูทิลิตี้:

stdbuf -oL python script.py > log

ตอนนี้ถ้าคุณtail -F logคุณควรเห็นแต่ละบรรทัดเอาท์พุททันทีที่มันถูกสร้างขึ้น


อีกทางหนึ่งที่ชัดเจนของการล้างกระแสออกหลังจากการพิมพ์แต่ละครั้งควรบรรลุเดียวกัน ดูเหมือนว่าsys.stdout.flush()จะประสบความสำเร็จใน Python หากคุณกำลังใช้งูหลาม 3.3 หรือใหม่กว่าที่printฟังก์ชั่นนอกจากนี้ยังมีคำหลักที่ไม่นี้:flushprint('hello', flush=True)


8
ขอบคุณฉันไม่รู้เกี่ยวกับบัฟเฟอร์! รู้อย่างนั้นแล้ว Google บอกฉันอย่างรวดเร็วว่านั่นpython -u script.pyเป็นการหลอกลวง แก้ไขคำตอบมากมายในครั้งเดียวฉันยอมรับคุณเพราะมันชี้ให้ฉันในทิศทางของการบัฟเฟอร์
บาร์ต

1
@ julbra เย็นใช่ฉันไม่ทราบว่าหลามมีตัวเลือกนั้น บางโปรแกรมบรรทัดคำสั่งนอกจากนี้ยังมีตัวเลือกที่คล้ายกัน - เช่น--line-bufferedสำหรับgrepแต่คนอื่น ๆ บางคนไม่ได้ stdbufคือยูทิลิตี catchall ทั่วไปที่ใช้จัดการกับสิ่งที่ไม่ต้องการ
Digital Trauma

@DigitalTrauma: มันจะดีกว่าหรือไม่ที่จะไม่ใช้บัฟเฟอร์ใด ๆ เช่นstdbuf -o0 python script.py > logในสถานการณ์ที่กำหนดเช่นนี้?
heemayl

@ heemayl -oLเป็นการประนีประนอม โดยทั่วไปแล้วบัฟเฟอร์ที่ใหญ่กว่าจะให้ประสิทธิภาพที่ดีกว่าเมื่อทำการเปลี่ยนเส้นทางบางแห่ง (การเรียกระบบน้อยลงและการดำเนินการ I / O น้อยลง) อย่างไรก็ตามหากมีความจำเป็นอย่างยิ่งที่จะต้องเห็นตัวละครแต่ละตัวในขณะที่มันถูกส่งออกแล้วใช่-o0จะต้อง
Digital Trauma

@Paul กรุณาหลีกเลี่ยงการคัดลอกเนื้อหาที่วางไว้ระหว่างคำตอบหรืออย่างน้อยที่สุดก็พูดถึงผู้แต่งดั้งเดิมที่ให้เนื้อหานั้น
Bakuriu

44

สิ่งนี้ควรทำงาน:

import time, sys
for i in range(10):
    print('bla')
    sys.stdout.flush()
    time.sleep(5)

เป็น Python จะบัฟเฟอร์stdoutตามค่าเริ่มต้นที่นี่ฉันได้ใช้sys.stdout.flush()เพื่อล้างบัฟเฟอร์

วิธีการแก้ปัญหาอีกก็จะไปใช้-u(unbuffered) pythonสวิทช์ของ ดังนั้นสิ่งต่อไปนี้ก็จะทำเช่นกัน:

python -u script.py >> log

11

การแปรผันของชุดรูปแบบของการใช้ตัวเลือกของ python สำหรับเอาต์พุตที่ไม่มีบัฟเฟอร์จะใช้#!/usr/bin/python -uเป็นบรรทัดแรก

ด้วย#!/usr/bin/env pythonอาร์กิวเมนต์พิเศษที่ไม่สามารถใช้งานได้ดังนั้นคุณสามารถรันPYTHONUNBUFFERED=1 ./my_scriipt.py > output.txtหรือทำได้สองขั้นตอน:

$ export PYTHONUNBUFFERED=1
$ ./myscript.py

10

คุณควรผ่านflush=Trueไปยังprintฟังก์ชั่น:

import time

for i in range(10):
    print('bla', flush=True)
    time.sleep(5)

ตามค่าเริ่มต้นแล้วเอกสารprintไม่ได้บังคับอะไรเกี่ยวกับการล้างข้อมูล:

ไม่ว่าเอาต์พุตจะถูกบัฟเฟอร์มักจะถูกกำหนดโดยไฟล์ แต่ถ้า flushอาร์กิวเมนต์คีย์เวิร์ดเป็นจริงสตรีมจะถูกล้างข้อมูลด้วยแรง

และเอกสารประกอบสำหรับsysstrems พูดว่า:

เมื่อมีการโต้ตอบสตรีมมาตรฐานจะถูก line-buffered มิฉะนั้นจะถูกบล็อกบัฟเฟอร์เหมือนไฟล์ข้อความทั่วไป คุณสามารถแทนที่ค่านี้ด้วย-uตัวเลือกบรรทัดคำสั่ง


หากคุณติดอยู่กับงูหลามรุ่นเก่าคุณต้องเรียกใช้flushเมธอดของsys.stdoutสตรีม:

import sys
import time

for i in range(10):
    print('bla')
    sys.stdout.flush()
    time.sleep(5)

1
อาร์กิวเมนต์ flush = True ทำงานได้ดีกับ Python 3.4.2 ไม่สามารถใช้งานได้กับโบราณ (.. ) Python 2.7.9
Bart

คำตอบนี้แสดงสิ่งเดียวกันกับที่DigitalTraumaบอกว่า 10 ชั่วโมงก่อนหน้า คุณควรโหวตโพสต์ของเขาอย่าโพสต์สิ่งเดียวกันอีก
dotancohen

4
@dotancohen จริงๆแล้วส่วนที่เกี่ยวกับprint(flush=True)คำตอบนั้นถูกเพิ่มเข้ามาหลังจากที่ฉันเขียนโดยบุคคลที่สาม ฉันพบว่ามันไม่ดีที่จะฉีกเนื้อหาจากคำตอบของฉันที่จะใส่ไว้ในที่อื่นโดยไม่ต้องเครดิต ฉันตัดสินใจที่จะเพิ่มคำตอบของฉันแต่เพียงผู้เดียวเพราะไม่มีคำตอบใด ๆ ที่กล่าวถึงวิธีที่ง่ายที่สุดในการบรรลุสิ่งที่ OP ต้องการในงูใหญ่รุ่นใหม่และฉันก็เพิ่ม "วิธีเก่า" เพื่อความสมบูรณ์ ในครั้งต่อไปโปรดตรวจสอบประวัติการแก้ไขก่อนที่จะแสดงความคิดเห็นและหรือ downvoting
Bakuriu

@Bakuriu: ฉันขอโทษแล้ว! นี้แสดงให้เห็นเหตุผลที่ดีที่มักจะโพสต์ทำไมเมื่อ downvoting คุณช่วยแก้ไขการโพสต์เล็กน้อยเพื่อให้ฉันสามารถเปลี่ยน downvote เป็น upvote ได้หรือไม่? ขอขอบคุณ!
dotancohen

มันควรจะทำงานกับงูหลาม 2.7 ถ้าคุณทำการนำเข้า:__future__ from __future__ import print_functionแต่ใช่สำหรับการเข้ากันได้กับ Python 3 เท่านั้น
Sergiy Kolodyazhnyy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.