Отключить буферизацию вывода

532

Включена ли буферизация вывода по умолчанию в интерпретаторе Python для sys.stdout?

Если ответ положительный, какие есть способы его отключения?

Предложения на данный момент:

  1. Используйте -uпереключатель командной строки
  2. Заворачивать sys.stdoutв объект, который очищается после каждой записи
  3. Установить PYTHONUNBUFFEREDenv var
  4. sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

Есть ли другой способ установить какой-либо глобальный флаг в sys/ sys.stdoutпрограммно во время выполнения?

Эли Бендерский
источник
7
Для `print 'в Python 3 смотрите этот ответ .
Антти Хаапала
1
Я думаю, что недостатком -uявляется то, что он не будет работать для скомпилированного байт-кода или для приложений с __main__.pyфайлом в качестве точки входа.
Ахан
Полная логика инициализации CPython находится здесь: github.com/python/cpython/blob/v3.8.2/Python/…
Бени Чернявский-Паскин

Ответы:

443

От Магнуса Лицки ответьте на рассылку :

Вы можете пропустить буферизацию для всего процесса python, используя "python -u" (или #! / Usr / bin / env python -u и т. Д.) Или установив переменную окружения PYTHONUNBUFFERED.

Вы также можете заменить sys.stdout другим потоком, таким как обертка, который выполняет сброс после каждого вызова.

class Unbuffered(object):
   def __init__(self, stream):
       self.stream = stream
   def write(self, data):
       self.stream.write(data)
       self.stream.flush()
   def writelines(self, datas):
       self.stream.writelines(datas)
       self.stream.flush()
   def __getattr__(self, attr):
       return getattr(self.stream, attr)

import sys
sys.stdout = Unbuffered(sys.stdout)
print 'Hello'
Себ
источник
71
Оригинальный sys.stdout по-прежнему доступен как sys .__ stdout__. На всякий случай, если вам это нужно =)
Антти Расинен
40
#!/usr/bin/env python -uне работает !! смотрите здесь
Вим
6
__getattr__просто чтобы избежать наследства ?!
Владимир Келешев,
33
Несколько замечаний, чтобы избежать головной боли: Как я заметил, буферизация вывода работает по-разному в зависимости от того, идет ли вывод в tty или другой процесс / канал. Если он идет к tty, то он сбрасывается после каждого \ n , но в канале он буферизуется. В последнем случае вы можете использовать эти промывочные растворы. В Cpython (не в pypy !!!): если вы перебираете ввод для строки for в sys.stdin: ..., тогда цикл for будет собирать количество строк перед выполнением тела цикла. Это будет вести себя как буферизация, хотя это скорее пакетная обработка. Вместо этого сделайте пока true: line = sys.stdin.readline ()
tzp
5
@tzp: вы можете использовать iter()вместо whileцикла: for line in iter(pipe.readline, ''):. Вам не нужно это на Python 3, где for line in pipe:дает как можно скорее.
JFS
122

Я предпочел бы поставить свой ответ в Как очистить вывод функции печати? или в функции печати Python, которая очищает буфер при вызове? , но так как они были помечены как дубликаты этого (что я не согласен), я отвечу здесь.

Начиная с Python 3.3, print () поддерживает ключевое слово аргумент "flush" ( см. Документацию ):

print('Hello World!', flush=True)
Cristóvão D. Sousa
источник
77
# reopen stdout file descriptor with write mode
# and 0 as the buffer size (unbuffered)
import io, os, sys
try:
    # Python 3, open as binary, then wrap in a TextIOWrapper with write-through.
    sys.stdout = io.TextIOWrapper(open(sys.stdout.fileno(), 'wb', 0), write_through=True)
    # If flushing on newlines is sufficient, as of 3.7 you can instead just call:
    # sys.stdout.reconfigure(line_buffering=True)
except TypeError:
    # Python 2
    sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

Кредиты: «Себастьян», где-то в списке рассылки Python.

Федерико А. Рампони
источник
В Python3 вы можете просто переопределить имя функции печати на значение сброса. Хотя это подвох!
meawoppl
16
@meawoppl: вы можете передавать flush=Trueпараметр в print()функцию начиная с Python 3.3.
Jfs
Редактирование ответа для показа ответа недопустимо в последней версии python
Майк
и os.fdopen(sys.stdout.fileno(), 'wb', 0)(обратите внимание bна двоичный файл) и flush=Trueработа для меня в 3.6.4. Однако, если вы используете подпроцесс для запуска другого скрипта, убедитесь, что вы указали python3, если у вас установлено несколько экземпляров python.
not2qubit
1
@ not2qubit: если вы используете, os.fdopen(sys.stdout.fileno(), 'wb', 0)вы получите двоичный объект файла, а не TextIOпоток. Вы должны будете добавить TextIOWrapperк миксу (убедитесь, что вы включили, write_throughчтобы исключить все буферы, или используйте line_buffering=Trueдля сброса только на новых строках).
Мартин Питерс
55

Да, это так.

Вы можете отключить его в командной строке с помощью ключа "-u".

В качестве альтернативы, вы можете вызывать .flush () для sys.stdout при каждой записи (или заключать его в объект, который делает это автоматически)

Брайан
источник
19

Это относится к ответу Кристована Д. Соузы, но я пока не могу комментировать.

Прямой вперед способ использования flushключевого слова аргумента Python 3 для того , чтобы всегда иметь небуферизованный выход есть:

import functools
print = functools.partial(print, flush=True)

после этого print всегда будет сбрасывать вывод напрямую (за исключением flush=Falseзаданного).

Обратите внимание, (а) что это отвечает на вопрос только частично, так как не перенаправляет весь вывод. Но я думаю, printчто это наиболее распространенный способ создания вывода в stdout/ stderrв Python, поэтому эти 2 строки охватывают, вероятно, большинство случаев использования.

Обратите внимание (б), что он работает только в модуле / скрипте, где вы его определили. Это может быть хорошо при написании модуля, так как он не связывается с sys.stdout.

Python 2 не предоставляет flushаргумент, но вы можете эмулировать функцию Python 3-type, printкак описано здесь https://stackoverflow.com/a/27991478/3734258 .

Тим
источник
1
За исключением того, что нет flush в python2 kwarg.
o11c
@ o11c, да, ты прав. Я был уверен, что проверил это, но каким-то образом я был озадачен (я изменил свой ответ, надеюсь, теперь все в порядке. Спасибо!
Тим
14
def disable_stdout_buffering():
    # Appending to gc.garbage is a way to stop an object from being
    # destroyed.  If the old sys.stdout is ever collected, it will
    # close() stdout, which is not good.
    gc.garbage.append(sys.stdout)
    sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

# Then this will give output in the correct order:
disable_stdout_buffering()
print "hello"
subprocess.call(["echo", "bye"])

Без сохранения старого sys.stdout disable_stdout_buffering () не идемпотентен, и множественные вызовы приведут к ошибке, подобной этой:

Traceback (most recent call last):
  File "test/buffering.py", line 17, in <module>
    print "hello"
IOError: [Errno 9] Bad file descriptor
close failed: [Errno 9] Bad file descriptor

Другая возможность:

def disable_stdout_buffering():
    fileno = sys.stdout.fileno()
    temp_fd = os.dup(fileno)
    sys.stdout.close()
    os.dup2(temp_fd, fileno)
    os.close(temp_fd)
    sys.stdout = os.fdopen(fileno, "w", 0)

(Присоединение к gc.garbage не очень хорошая идея, потому что это место, где помещаются несвободные циклы, и вы можете проверить их.)

Марк Сиборн
источник
2
Если старое stdoutвсе еще живет, sys.__stdout__как некоторые предполагали, мусор не понадобится, верно? Это крутой трюк, хотя.
Томас Але
1
Как и в случае ответа @ Federico, это не будет работать с Python 3, так как ValueError: can't have unbuffered text I/Oпри вызове возникнет исключение print().
gbmhunter
Ваша «другая возможность» на первый взгляд кажется наиболее надежным решением, но, к сожалению, она страдает от состояния гонки в случае, когда другой поток вызывает open () после вашего sys.stdout.close () и перед вашим os.dup2 (temp_fd, fileno ). Я узнал об этом, когда попытался использовать вашу технику под ThreadSanitizer, которая делает именно это. Ошибка становится громче из-за того, что dup2 () завершается ошибкой с EBUSY, когда он гоняется с open () вот так; см. stackoverflow.com/questions/23440216/…
Дон Хэтч
13

Следующие работы в Python 2.6, 2.7 и 3.2:

import os
import sys
buf_arg = 0
if sys.version_info[0] == 3:
    os.environ['PYTHONUNBUFFERED'] = '1'
    buf_arg = 1
sys.stdout = os.fdopen(sys.stdout.fileno(), 'a+', buf_arg)
sys.stderr = os.fdopen(sys.stderr.fileno(), 'a+', buf_arg)
Gummbum
источник
Запустите это дважды, и оно вылетит на окнах :-)
Майкл Клеркс
@MichaelClerx Ммм хм, всегда не забывайте закрывать свои файлы xD.
Python 3.5 на Raspbian 9 дает мне OSError: [Errno 29] Illegal seekдля строкиsys.stdout = os.fdopen(sys.stdout.fileno(), 'a+', buf_arg)
sdbbs
12

Да, он включен по умолчанию. Вы можете отключить его, используя опцию -u в командной строке при вызове python.

Натан
источник
7

Вы также можете запустить Python с помощью утилиты stdbuf :

stdbuf -oL python <script>

dyomas
источник
2
Буферизация строки (при -oLвключении) по-прежнему буферизируется - см. F / e stackoverflow.com/questions/58416853/… , спрашивающий, почему end=''вывод больше не отображается немедленно.
Чарльз Даффи
Верно, но буферизация строки используется по умолчанию (с tty), поэтому имеет ли смысл писать код, предполагая, что вывод полностью не буферизован - может быть, лучше явно print(..., end='', flush=True)указать, где это имеет значение? OTOH, когда несколько программ записывают в один и тот же выход одновременно, компромисс имеет тенденцию переходить от непосредственного прогресса к уменьшению путаницы вывода, и буферизация строки становится привлекательной. Так что, может быть , это лучше не писать явно flushи управление буферизацией внешне?
Бени Чернявский-Паскин
Я думаю нет. Сам процесс должен решить, когда и почему он вызывает flush. Внешнее управление буферизацией здесь является обязательным обходным
путем
7

В Python 3 вы можете «обезьянько-патчить» функцию печати, чтобы всегда отправлять flush = True:

_orig_print = print

def print(*args, **kwargs):
    _orig_print(*args, flush=True, **kwargs)

Как указано в комментарии, вы можете упростить это, привязав параметр flush к значению через functools.partial:

print = functools.partial(print, flush=True)
Оливер
источник
3
Просто интересно, но разве это не идеальный вариант использования functools.partial?
0xC0000022L
Спасибо @ 0xC0000022L, это выглядит лучше! print = functools.partial(print, flush=True)у меня отлично работает
MarSoft
@ 0xC0000022L действительно, я обновил пост, чтобы показать эту опцию, спасибо за указание на это
Оливер
3
Если вы хотите, чтобы это применялось повсюду,import builtins; builtins.print = partial(print, flush=True)
Perkins
4

Вы также можете использовать fcntl для изменения файловых флагов на лету.

fl = fcntl.fcntl(fd.fileno(), fcntl.F_GETFL)
fl |= os.O_SYNC # or os.O_DSYNC (if you don't care the file timestamp updates)
fcntl.fcntl(fd.fileno(), fcntl.F_SETFL, fl)
jimx
источник
1
Есть эквивалент Windows: stackoverflow.com/questions/881696/…
Тобу
12
O_SYNC не имеет ничего общего с буферизацией на уровне пользователя, о которой спрашивает этот вопрос.
Апенварр
4

Можно переопределить только write метод, sys.stdoutкоторый вызывает flush. Предлагаемый способ реализации приведен ниже.

def write_flush(args, w=stdout.write):
    w(args)
    stdout.flush()

Значение wаргумента по умолчанию будет сохранять исходную writeссылку на метод. После write_flush определения оригинал writeможет быть переопределен.

stdout.write = write_flush

Код предполагает, что stdoutимпортируется таким образом from sys import stdout.

Василий Е.
источник
3

Вы можете создать небуферизованный файл и назначить этот файл для sys.stdout.

import sys 
myFile= open( "a.log", "w", 0 ) 
sys.stdout= myFile

Вы не можете волшебным образом изменить системный стандартный вывод; так как он поставляется вашей программой на Python операционной системой.

С. Лотт
источник
3

Вариант, который работает без сбоев (по крайней мере, на win32; python 2.7, ipython 0.12), затем вызывается впоследствии (несколько раз):

def DisOutBuffering():
    if sys.stdout.name == '<stdout>':
        sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

    if sys.stderr.name == '<stderr>':
        sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', 0)
Laimis
источник
Вы уверены, что это не буферизовано?
квант
1
Вы должны проверить sys.stdout is sys.__stdout__вместо того, чтобы полагаться на объект замены, имеющий атрибут имени?
Leewz
это прекрасно работает, если gunicorn по какой-то причине не уважает PYTHONUNBUFFERED.
Брайан Арсуага
3

(Я разместил комментарий, но он как-то потерялся. Итак, снова :)

  1. Как я заметил, CPython (по крайней мере, в Linux) ведет себя по-разному в зависимости от того, куда выводятся данные. Если он переходит к tty, то вывод сбрасывается после каждого ' \n'
    Если он идет в канал / процесс, тогда он буферизуется, и вы можете использовать flush()основанные решения или опцию -u, рекомендованную выше.

  2. Немного связано с буферизацией вывода:
    если вы перебираете строки на входе с

    for line in sys.stdin:
    ...

затем реализация for в CPython будет некоторое время собирать входные данные, а затем выполнять тело цикла для набора входных строк. Если ваш сценарий собирается записать вывод для каждой строки ввода, это может выглядеть как буферизация вывода, но на самом деле это пакетная обработка, и поэтому ни один из flush()методов и т. Д. Не поможет. Интересно, что у вас нет такого поведения в Pypy . Чтобы избежать этого, вы можете использовать

while True: line=sys.stdin.readline()
...

TZP
источник
вот ваш комментарий . Это может быть ошибка в старых версиях Python. Не могли бы вы предоставить пример кода? Что-то вроде for line in sys.stdinпротивfor line in iter(sys.stdin.readline, "")
JFS
для строки в sys.stdin: print ("Строка:" + строка); sys.stdout.flush ()
tzp
это похоже на ошибку опережающего чтения . Это должно происходить только на Python 2 и, если stdin - это канал. Код в моем предыдущем комментарии демонстрирует проблему ( for line in sys.stdinпредоставляет отложенный ответ)
jfs
2

Один из способов получить небуферизованный вывод - использовать sys.stderrвместо sys.stdoutили просто вызватьsys.stdout.flush() чтобы явно вызвать запись.

Вы можете легко перенаправить все напечатанное, выполнив:

import sys; sys.stdout = sys.stderr
print "Hello World!"

Или перенаправить только для конкретного printзаявления:

print >>sys.stderr, "Hello World!"

Чтобы сбросить стандартный вывод, вы можете просто сделать:

sys.stdout = sys.__stdout__
STDERR
источник
1
Это может привести в замешательство, когда вы позже попытаетесь захватить выходные данные, используя стандартное перенаправление, и обнаружите, что ничего не захватываете! ps твой stdout наглухо и прочее.
свободное пространство
1
Одно большое предостережение относительно выборочной печати в stderr заключается в том, что это приводит к тому, что строки появляются не на своем месте, поэтому, если у вас также нет метки времени, это может привести к путанице.
haridsv