Вимкнути вихідну буферизацію


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
Для "друку" в Python 3 дивіться цю відповідь .
Антті Хаапала

1
Я думаю, що недоліком -uє те, що він не працюватиме для компільованого байт-коду або для додатків, що мають __main__.pyфайл в якості точки входу.
ахан

Повна логіка ініціалізації CPython тут: github.com/python/cpython/blob/v3.8.2/Python/…
Бені Чернявський-Паскін

Відповіді:


443

Від Magnus Lycka відповідь на список розсилки :

Ви можете пропустити буферизацію для всього процесу 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не працює !! дивіться тут
wim

6
__getattr__просто щоб уникнути спадкування ?!
Володимир Келешев

32
Деякі зауваження, щоб врятувати деякі головні болі: Як я помітив, буферизація вихідних даних працює по-різному, залежно від того, чи буде вихід на Tty чи інший процес / трубу. Якщо вона переходить до tty, вона змивається після кожного \ n , але в трубі вона буферна. В останньому випадку ви можете використовувати ці промивні розчини. У Cpython (не в pypy !!!): Якщо ви повторите вхід з для для рядка в 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)

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лише для розробки в нових рядках).
Martijn Pieters

55

Так.

Ви можете відключити його в командному рядку за допомогою перемикача "-u".

Крім того, ви можете викликати .flush () на sys.stdout під час кожного запису (або обгорнути його об'єктом, який робить це автоматично)


19

Це стосується відповіді Крістовау Д. Соуса, але я поки не міг прокоментувати.

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

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

після цього друк завжди буде прямо вимикати висновки (крім flush=Falseвказаного).

Зауважте, (а), що це відповідає на питання лише частково, оскільки не перенаправляє весь результат. Але я здогадуюсь, printце найпоширеніший спосіб створення виводу в stdout/ stderrу python, тому ці 2 рядки охоплюють, мабуть, більшість випадків використання.

Зауважте (б), що він працює лише в модулі / скрипті, де ви його визначили. Це може бути добре при написанні модуля, оскільки він не псується з sys.stdout.

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


1
За винятком того, що в flushpython2 немає kwarg.
o11c

@ o11c, так, ти маєш рацію. Я був упевнений, що перевірив це, але якось я, здавалося б, розгубився (: я змінив свою відповідь, сподіваюся, що це зараз добре. Дякую!
tim

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, enable_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, коли він працює з відкритим () таким чином; дивіться 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)

Запустіть це двічі, і воно
вибивається

@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>


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)

Як зазначено в коментарі, ви можете спростити це, прив’язавши параметр флеш до значення за допомогою 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)
Перкінс

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)

1
Є еквівалент Windows: stackoverflow.com/questions/881696/…
Тобу,

12
O_SYNC взагалі не має нічого спільного з буферизацією на рівні користувачів, про яку задається це питання.
apenwarr

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

Ви не можете чарівно змінити поданий системою stdout; оскільки він постачається до вашої програми 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)

Ви впевнені, що це не буфер?
квантовий

1
Чи слід перевіряти sys.stdout is sys.__stdout__замість того, щоб покладатися на об'єкт заміни, який має атрибут імені?
leewz

це чудово працює, якщо зброя чомусь не поважає PYTHONUNBUFFERED.
Брайан Арсуга

3

(Я опублікував коментар, але він якось загубився. Отже, знову :)

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

  2. Трохи пов'язаний з буферизацією виводу:
    Якщо ви повторите рядки на вході з

    for line in sys.stdin:
    ...

то для реалізації в CPython деякий час буде збирати вхід, а потім виконувати тіло циклу для куки вхідних рядків. Якщо ваш сценарій збирається записати вихід для кожного рядка введення, це може виглядати як буферизація вихідних даних, але насправді це пакетне завантаження, і, отже, жодна із flush()методик тощо не допоможе в цьому. Цікаво, що ви не маєте такої поведінки у pypy . Щоб цього уникнути, можна використовувати

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


ось ваш коментар . Це може бути помилка в старих версіях Python. Чи можете ви надати приклад коду? Щось на кшталт for line in sys.stdinvs.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!"

Щоб скинути stdout, ви можете просто зробити:

sys.stdout = sys.__stdout__

1
Це може стати дуже заплутаним, коли ви пізніше спробуєте зафіксувати вихід, використовуючи стандартне перенаправлення, і виявите, що ви нічого не фіксуєте! ps ваш штрих підкреслюється жирним шрифтом.
простір

1
Однією з великих обережностей щодо вибіркової друку на stderr є те, що це призводить до того, що рядки з’являться не на місці, тому, якщо у вас також є часові позначки, це може стати дуже заплутаним.
haridsv
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.