Напишите Python stdout в файл немедленно

51

При попытке записать стандартный вывод из скрипта Python в текстовый файл ( python script.py > log) текстовый файл создается при запуске команды, но фактическое содержимое не записывается до тех пор, пока скрипт Python не завершится. Например:

script.py:

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

выводит на стандартный вывод каждые 5 секунд при вызове с помощью python script.py, но когда я вызываю python script.py > log, размер файла журнала остается нулевым, пока сценарий не завершится. Можно ли напрямую записывать в файл журнала, чтобы вы могли следить за ходом сценария (например, используя tail)?

РЕДАКТИРОВАТЬ Оказывается, что python -u script.pyделает трюк, я не знал о буферизации стандартного вывода.

Барт
источник
1
@jezmck, я мог неправильно понять вопрос.
Zyxue

Ответы:

64

Это происходит потому, что обычно, когда процесс STDOUT перенаправляется не на терминал, а в выходной файл, он буферизируется в некотором буфере определенного размера ОС (во многих случаях, возможно, 4 или 8 КБ). И наоборот, при выводе на терминал STDOUT будет буферизован строкой или не буферизован вообще, поэтому вы увидите вывод после каждого \nили для каждого символа.

Обычно вы можете изменить буферизацию STDOUT с помощью stdbufутилиты:

stdbuf -oL python script.py > log

Теперь, если вы tail -F log, вы должны увидеть каждую строку вывода сразу же, как он генерируется.


Альтернативно явная очистка выходного потока после каждого отпечатка должна достигать того же самого. Похоже, sys.stdout.flush()следует достичь этого в Python. Если вы используете Python 3.3 или более позднюю версию, то printфункция также имеет flushключевое слово , которое делает это: print('hello', flush=True).

Цифровая травма
источник
8
Спасибо, я не знал о буферизации! Зная это, Google довольно быстро сказал мне, что python -u script.pyделает свое дело. РЕДАКТИРОВАТЬ Так много ответов сразу, я принял ваш, так как он указал мне в направлении буферизации.
Барт
1
@julbra Круто, да, я не знал, что у python есть такая опция. Некоторые программы командной строки также имеют аналогичные параметры - например, --line-bufferedдля grep, но некоторые другие нет. stdbufэто общая утилита для борьбы с теми, кто этого не делает.
Цифровая травма
@DigitalTrauma: не лучше ли вообще не использовать буферизацию, то есть stdbuf -o0 python script.py > logв таких обстоятельствах?
Heemayl
@heemayl -oL- это компромисс. Как правило, большие буферы обеспечивают лучшую производительность при перенаправлении куда-либо (меньше системных вызовов и меньше операций ввода-вывода). Однако если абсолютно необходимо видеть каждый символ как выводимый, тогда да, -o0потребуется.
Цифровая травма
@Paul Пожалуйста, избегайте копировать вставку содержимого между ответами, или хотя бы упоминать авторов, предоставивших контент.
Бакуриу
44

Это должно сделать работу:

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

Поскольку Python будет буферизовать stdoutпо умолчанию, здесь я использовал sys.stdout.flush()для очистки буфера.

Другое решение было бы использовать -u(небуферизованный) переключатель python. Итак, следующее тоже подойдет:

python -u script.py >> log
heemayl
источник
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аргумент ключевого слова равен true, поток принудительно сбрасывается.

И документация для sysРоссии гласит:

В интерактивном режиме стандартные потоки буферизуются по строкам. В противном случае они буферизируются как обычные текстовые файлы. Вы можете переопределить это значение с помощью параметра -uкомандной строки.


Если вы застряли в древней версии Python, вам нужно вызвать flushметод sys.stdoutпотока:

import sys
import time

for i in range(10):
    print('bla')
    sys.stdout.flush()
    time.sleep(5)
Bakuriu
источник
1
Аргумент flush = True прекрасно работает с Python 3.4.2, на самом деле не работает с древним (..) Python 2.7.9
Барт
Этот ответ предполагает то же самое, что было DigitalTraumaсказано за 10 часов до этого. Вы должны проголосовать за его пост, а не публиковать то же самое снова.
dotancohen
4
@dotancohen На самом деле часть о print(flush=True)была добавлена ​​к этому ответу после моего от стороннего автора. Я считаю плохим вкусом вырывать содержимое из моего ответа, вставляя его в другой без кредита. Я решил добавить свой ответ исключительно потому, что ни в одном ответе не было упоминания о самом простом способе достижения того, что OP хотел получить в более новых версиях python, и я добавил «старый способ» только для полноты. В следующий раз, пожалуйста, проверьте историю изменений, прежде чем комментировать и / или понизить.
Бакуриу
@Bakuriu: Извините! Это показывает хорошую причину, чтобы всегда публиковать, почему при голосовании . Не могли бы вы отредактировать пост немного, чтобы я мог изменить свое понижение на повышение? Спасибо!
dotancohen
Он должен работать с Python 2.7 , если вы делаете __future__импорт: from __future__ import print_function. Но да, это только для совместимости с Python 3
Сергей Колодяжный