Viết stdout Python để nộp ngay lập tức


51

Khi cố gắng viết thiết bị xuất chuẩn từ tập lệnh Python sang tệp văn bản ( python script.py > log), tệp văn bản được tạo khi lệnh được bắt đầu, nhưng nội dung thực tế không được viết cho đến khi tập lệnh Python kết thúc. Ví dụ:

script.py:

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

in ra thiết bị xuất chuẩn cứ sau 5 giây khi được gọi với python script.py, nhưng khi tôi gọi python script.py > log, kích thước của tệp nhật ký sẽ bằng 0 cho đến khi tập lệnh kết thúc. Có thể ghi trực tiếp vào tệp nhật ký, để bạn có thể theo dõi tiến trình của tập lệnh (ví dụ: sử dụng tail) không?

EDIT Hóa ra đó python -u script.pylà mánh khóe, tôi không biết về bộ đệm của thiết bị xuất chuẩn.


1
@jezmck, tôi có thể hiểu câu hỏi sai.
zyxue

Câu trả lời:


64

Điều này xảy ra bởi vì thông thường khi quá trình STDOUT được chuyển hướng đến một thứ khác ngoài thiết bị đầu cuối, thì đầu ra được đệm vào một số bộ đệm có kích thước cụ thể của hệ điều hành (có thể là 4k hoặc 8k trong nhiều trường hợp). Ngược lại, khi xuất ra một thiết bị đầu cuối, STDOUT sẽ được đệm theo dòng hoặc hoàn toàn không được đệm, vì vậy bạn sẽ thấy đầu ra sau mỗi \nhoặc cho mỗi ký tự.

Nói chung, bạn có thể thay đổi bộ đệm STDOUT với stdbuftiện ích:

stdbuf -oL python script.py > log

Bây giờ nếu bạn tail -F log, bạn sẽ thấy từng đầu ra dòng ngay lập tức khi nó được tạo.


Cách xả dòng rõ ràng khác sau mỗi lần in sẽ đạt được như nhau. Có vẻ như sys.stdout.flush()sẽ đạt được điều này trong Python. Nếu bạn đang sử dụng Python 3.3 hoặc mới hơn, printhàm cũng có một flushtừ khóa thực hiện điều này : print('hello', flush=True).


8
Cảm ơn, tôi không biết về bộ đệm! Biết điều đó, Google đã nhanh chóng nói với tôi rằng đó là python -u script.pymột mánh khóe. EDIT Rất nhiều câu trả lời cùng một lúc, tôi đã chấp nhận câu trả lời của bạn vì nó chỉ cho tôi hướng đệm.
Bart

1
@julbra Thật tuyệt, vâng tôi cũng không biết trăn có lựa chọn đó. Một số chương trình dòng lệnh cũng có các tùy chọn tương tự - ví dụ như --line-bufferedcho grep, nhưng một số khác thì không. stdbuflà tiện ích chung chung để đối phó với những thứ không có.
Chấn thương kỹ thuật số

@DigitalTrauma: Không tốt hơn là không sử dụng bộ đệm nào cả, ví dụ như stdbuf -o0 python script.py > logtrong trường hợp xác định này?
heemayl

@heemayl -oLlà một sự thỏa hiệp. Nói chung, bộ đệm lớn hơn sẽ cung cấp hiệu suất tốt hơn khi chuyển hướng ở đâu đó (ít cuộc gọi hệ thống hơn và ít thao tác I / O hơn). Tuy nhiên, nếu nhất thiết phải xem từng ký tự vì nó là đầu ra thì có, -o0sẽ được yêu cầu.
Chấn thương kỹ thuật số

@Paul Vui lòng tránh sao chép nội dung dán giữa các câu trả lời, hoặc ít nhất là đề cập đến các tác giả gốc đã cung cấp nội dung.
Bakuriu

44

Điều này sẽ làm công việc:

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

Vì Python sẽ đệm stdouttheo mặc định, ở đây tôi đã sử dụng sys.stdout.flush()để xóa bộ đệm.

Một giải pháp khác là sử dụng công tắc -u(không có bộ đệm) của python. Vì vậy, sau đây sẽ làm quá:

python -u script.py >> log

11

Sự thay đổi về chủ đề sử dụng tùy chọn riêng của python cho đầu ra không có bộ đệm sẽ được sử dụng #!/usr/bin/python -ulàm dòng đầu tiên.

Với #!/usr/bin/env pythonđối số bổ sung đó sẽ không hoạt động, do đó, một cách khác, người ta có thể chạy PYTHONUNBUFFERED=1 ./my_scriipt.py > output.txthoặc thực hiện theo hai bước:

$ export PYTHONUNBUFFERED=1
$ ./myscript.py

10

Bạn nên chuyển flush=Trueđến printhàm:

import time

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

Theo tài liệu, theo mặc định, printkhông thực thi bất cứ điều gì về việc xả nước:

Cho dù đầu ra được đệm thường được xác định bởi tệp, nhưng nếu flushđối số từ khóa là đúng, luồng sẽ bị xóa.

Và tài liệu cho syscác strems nói:

Khi tương tác, các luồng tiêu chuẩn được đệm dòng. Mặt khác, chúng được đệm theo khối như các tệp văn bản thông thường. Bạn có thể ghi đè giá trị này bằng -utùy chọn dòng lệnh.


Nếu bạn bị mắc kẹt với một phiên bản cổ của trăn, bạn phải gọi flushphương thức của sys.stdoutluồng:

import sys
import time

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

1
Đối số flush = True hoạt động độc đáo với Python 3.4.2, thực sự không hoạt động với Python cổ đại (..) 2.7.9
Bart

Câu trả lời này cho thấy điều tương tự đã DigitalTraumanói 10 giờ trước. Bạn nên upvote bài viết của anh ấy, không đăng điều tương tự một lần nữa.
dotancohen

4
@dotancohen Trên thực tế, phần về print(flush=True)đã được thêm vào câu trả lời đó sau của tôi bởi một tác giả bên thứ ba. Tôi thấy thật tệ khi trích xuất nội dung từ câu trả lời của mình để đưa chúng vào một mục khác mà không có tín dụng. Tôi quyết định thêm câu trả lời của tôi chỉ vì không có câu trả lời cung cấp bất kỳ đề cập đến trong những cách đơn giản nhất để đạt được những gì OP muốn trong các phiên bản mới hơn của trăn, và tôi thêm "cách cũ" chỉ cho đầy đủ. Lần sau, vui lòng kiểm tra lịch sử sửa đổi trước khi bình luận và hoặc bỏ phiếu.
Bakuriu

@Bakuriu: Tôi xin lỗi rồi! Điều này cho thấy một lý do tốt để luôn luôn đăng tại sao khi downvote . Bạn có thể vui lòng chỉnh sửa bài đăng một chút để tôi có thể thay đổi downvote của mình thành upvote không? Cảm ơn bạn!
dotancohen

Nó sẽ hoạt động với Python 2.7 nếu bạn __future__nhập : from __future__ import print_function. Nhưng vâng, đó chỉ là khả năng tương thích với Python 3
Sergiy Kolodyazhnyy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.