Wyłącz buforowanie danych wyjściowych

532

Czy buforowanie danych wyjściowych jest domyślnie włączone dla interpretera Pythona sys.stdout?

Jeśli odpowiedź jest pozytywna, jakie są wszystkie sposoby jej wyłączenia?

Dotychczasowe sugestie:

  1. Użyj -uprzełącznika wiersza polecenia
  2. Zawiń sys.stdoutw obiekt, który opróżnia się po każdym zapisie
  3. Ustaw PYTHONUNBUFFEREDenv var
  4. sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

Czy jest jakiś inny sposób na ustawienie globalnej flagi sys/ sys.stdoutprogramowo podczas wykonywania?

Eli Bendersky
źródło
7
Aby uzyskać `print 'w Pythonie 3, zobacz tę odpowiedź .
Antti Haapala
1
Myślę, że wadą -ujest to, że nie będzie działać w przypadku skompilowanego kodu bajtowego lub aplikacji z __main__.pyplikiem jako punktem wejścia.
akhan
Pełna logika inicjalizacji CPython znajduje się tutaj: github.com/python/cpython/blob/v3.8.2/Python/...
Beni Cherniavsky-Paskin

Odpowiedzi:

443

Od Magnusa Lycki odpowiedź na liście mailingowej :

Możesz pominąć buforowanie dla całego procesu Pythona, używając „python -u” (lub #! / Usr / bin / env python -u itp.) Lub ustawiając zmienną środowiskową PYTHONUNBUFFERED.

Możesz także zastąpić sys.stdout innym strumieniem, takim jak wrapper, który wykonuje flush po każdym wywołaniu.

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'
Seb
źródło
71
Oryginalny sys.stdout jest nadal dostępny jako sys .__ stdout__. Na wszelki wypadek =)
Antti Rasinen,
40
#!/usr/bin/env python -unie działa !! patrz tutaj
wim
6
__getattr__żeby uniknąć dziedziczenia ?!
Vladimir Keleshev
32
Kilka uwag, aby zaoszczędzić trochę problemów: Jak zauważyłem, buforowanie danych wyjściowych działa inaczej w zależności od tego, czy dane wyjściowe trafią do tty lub innego procesu / potoku. Jeśli przejdzie do tty, jest spłukiwany po każdym \ n , ale w potoku jest buforowany. W tym drugim przypadku możesz skorzystać z tych rozwiązań spłukujących. W Cpython (nie w pypy !!!): Jeśli wykonasz iterację po wejściu z linią for w sys.stdin: ... to pętla for zbierze kilka linii przed uruchomieniem treści pętli. Będzie to zachowywać się jak buforowanie, choć jest raczej grupowe. Zamiast tego wykonaj while true: line = sys.stdin.readline ()
tzp
5
@tzp: można użyć iter()zamiast whilepętli: for line in iter(pipe.readline, ''):. Nie potrzebujesz go w Pythonie 3, gdzie for line in pipe:daje to tak szybko, jak to możliwe.
jfs
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)

Kredyty: „Sebastian”, gdzieś na liście dyskusyjnej Python.

Federico A. Ramponi
źródło
W Python3 możesz po prostu zastąpić nazwę funkcji drukowania opróżniającą ją funkcją. To brudna sztuczka!
meawoppl
16
@meawoppl: można przekazać flush=Trueparametr do print()działania od wersji Python 3.3.
jfs
Edycja odpowiedzi na odpowiedź show nie jest poprawna w najnowszej wersji Pythona
Mike
zarówno os.fdopen(sys.stdout.fileno(), 'wb', 0)(zwróć uwagę bna binarne), jak i flush=Truepraca dla mnie w 3.6.4. Jeśli jednak używasz podprocesu do uruchomienia innego skryptu, upewnij się, że podałeś python3, jeśli masz zainstalowanych wiele instancji Pythona.
not2qubit
1
@ not2qubit: jeśli go użyjesz os.fdopen(sys.stdout.fileno(), 'wb', 0), skończysz z obiektem pliku binarnego, a nie ze TextIOstrumieniem. Trzeba by dodać TextIOWrappermiks (upewniając się, że write_throughmożna wyeliminować wszystkie bufory lub użyć line_buffering=Truetylko do koloru na nowych liniach).
Martijn Pieters
55

Tak to jest.

Możesz go wyłączyć w wierszu polecenia za pomocą przełącznika „-u”.

Alternatywnie możesz wywołać .flush () na sys.stdout przy każdym zapisie (lub owinąć go obiektem, który robi to automatycznie)

Brian
źródło
19

Odnosi się to do odpowiedzi Cristóvão D. Sousy, ale nie mogłem jeszcze komentować.

Prosty sposób użycia flushargumentu słowa kluczowego w Pythonie 3 , aby zawsze mieć niebuforowane dane wyjściowe, jest:

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

potem wydruk zawsze będzie bezpośrednio wylewał wydruk (z wyjątkiem tego, że flush=Falsepodano).

Zauważ, (a), że to odpowiada tylko częściowo na pytanie, ponieważ nie przekierowuje wszystkich wyników. Ale przypuszczamprint wydaje jest to najczęstszy sposób tworzenia danych wyjściowych do stdout/ stderrw pythonie, więc te 2 linie obejmują prawdopodobnie większość przypadków użycia.

Uwaga (b), że działa tylko w module / skrypcie, w którym został zdefiniowany. Może to być dobre podczas pisania modułu, ponieważ nie zadziera zsys.stdout .

Python 2 nie podaje flushargumentu, ale możesz emulować funkcję typu Python 3, printjak opisano tutaj https://stackoverflow.com/a/27991478/3734258 .

tim
źródło
1
Tyle że flushw python2 nie ma kwarga.
o11c
@ o11c, tak masz rację. Byłem pewien, że go przetestowałem, ale jakoś pozornie byłem zdezorientowany (: Zmieniłem odpowiedź, mam nadzieję, że teraz jest w porządku. Dzięki!
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"])

Bez zapisania starego sys.stdout funkcja disable_stdout_buffering () nie jest idempotentna, a wiele wywołań spowoduje błąd taki jak ten:

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

Inną możliwością jest:

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)

(Dołączanie do gc.garbage nie jest dobrym pomysłem, ponieważ tam umieszczane są cykle niemożliwe do zniesienia i możesz je sprawdzić).

Mark Seaborn
źródło
2
Jeśli stare stdoutnadal żyje, sys.__stdout__jak sugerują niektórzy, śmieci nie będą konieczne, prawda? To fajna sztuczka.
Thomas Ahle
1
Podobnie jak w przypadku odpowiedzi @ Federico, nie zadziała to w Pythonie 3, ponieważ spowoduje zgłoszenie wyjątku ValueError: can't have unbuffered text I/Opodczas wywoływania print().
gbmhunter
Twoja „inna możliwość” wydaje się na początku jak najbardziej niezawodne rozwiązanie, ale niestety cierpi z powodu wyścigu w przypadku, gdy inny wątek wywołuje open () po twoim sys.stdout.close () i przed twoim os.dup2 (temp_fd, fileno ). Dowiedziałem się tego, gdy próbowałem użyć Twojej techniki w ThreadSanitizer, co dokładnie to robi. Awarię pogłębia fakt, że dup2 () kończy się niepowodzeniem z EBUSY, gdy działa w taki sposób z open (); patrz stackoverflow.com/questions/23440216/…
Don Hatch
13

Poniższe działa w Pythonie 2.6, 2.7 i 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
źródło
Uruchom to dwukrotnie i zawiesza się w
systemie
@MichaelClerx Mmm hmm, zawsze pamiętaj o zamknięciu plików xD.
Python 3.5 na Raspbian 9 daje mi OSError: [Errno 29] Illegal seeklinięsys.stdout = os.fdopen(sys.stdout.fileno(), 'a+', buf_arg)
sdbbs,
12

Tak, jest domyślnie włączony. Możesz go wyłączyć, używając opcji -u w wierszu poleceń podczas wywoływania Pythona.

Nathan
źródło
7

Możesz również uruchomić Python za pomocą narzędzia stdbuf :

stdbuf -oL python <script>

dyomy
źródło
2
Buforowanie linii (jako -oLwłączone) jest nadal buforowane - patrz f / e stackoverflow.com/questions/58416853/... , pytając, dlaczego end=''wyjście nie jest natychmiast wyświetlane.
Charles Duffy
To prawda, ale buforowanie wierszy jest domyślnym (z tty), więc czy warto pisać kod, zakładając, że dane wyjściowe są całkowicie niebuforowane - może lepiej jawnie określić, print(..., end='', flush=True)gdzie jest to niepraktyczne? OTOH, gdy kilka programów jednocześnie pisze na tym samym wyjściu, kompromis ma tendencję do przechodzenia od natychmiastowego postępu do zmniejszania pomieszania wyników, a buforowanie linii staje się atrakcyjne. Więc może to lepiej nie pisać jawne flushi kontrola buforowanie zewnętrznie?
Beni Cherniavsky-Paskin
Myśle że nie. Sam proces powinien decydować, kiedy i dlaczego wywołuje flush. Zewnętrzne sterowanie buforowaniem jest tutaj
obowiązkowe
7

W Pythonie 3 możesz małpować łatanie funkcji drukowania, aby zawsze wysyłać flush = True:

_orig_print = print

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

Jak wskazano w komentarzu, można to uprościć, wiążąc parametr flush z wartością za pomocą functools.partial:

print = functools.partial(print, flush=True)
Oliver
źródło
3
Zastanawiam się, ale czy nie byłby to idealny przypadek użycia functools.partial?
0xC0000022L
Dzięki @ 0xC0000022L, dzięki temu wygląda lepiej! print = functools.partial(print, flush=True)działa dobrze dla mnie.
MarSoft,
@ 0xC0000022L, rzeczywiście, zaktualizowałem post, aby pokazać tę opcję, dziękuję za zwrócenie na to uwagi
Oliver
3
Jeśli chcesz, żeby miało to zastosowanie wszędzieimport builtins; builtins.print = partial(print, flush=True)
Perkins,
4

Możesz także użyć fcntl, aby zmienić flagi plików w locie.

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
źródło
1
Istnieje odpowiednik systemu Windows: stackoverflow.com/questions/881696/...
Tobu
12
O_SYNC nie ma nic wspólnego z buforowaniem na poziomie przestrzeni użytkownika, o które pyta to pytanie.
apenwarr
4

Możliwe jest zastąpienie tylko write metody sys.stdoutz tą, która wywołuje flush. Sugerowana implementacja metody znajduje się poniżej.

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

Domyślna wartość wargumentu zachowa oryginalne writeodniesienie do metody. Po write_flush zdefiniowaniu oryginał writemoże zostać zastąpiony.

stdout.write = write_flush

Kod zakłada, że stdoutjest importowany w ten sposób from sys import stdout.

Wasilij E.
źródło
3

Możesz utworzyć niebuforowany plik i przypisać ten plik do sys.stdout.

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

Nie można magicznie zmienić standardowego wejścia dostarczonego przez system; ponieważ jest dostarczany do twojego programu python przez system operacyjny.

S.Lott
źródło
3

Wariant, który działa bez awarii (przynajmniej w win32; python 2.7, ipython 0.12), a następnie wywoływany (wielokrotnie):

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
źródło
Czy jesteś pewien, że to nie jest buforowane?
kwantowy
1
Czy powinieneś sprawdzić sys.stdout is sys.__stdout__zamiast polegać na zamiennym obiekcie posiadającym atrybut nazwy?
leewz
działa to świetnie, jeśli gunicorn z jakiegoś powodu nie przestrzega PYTHONUNBUFFERED.
Brian Arsuaga,
3

(Opublikowałem komentarz, ale jakoś się zgubił. Więc jeszcze raz :)

  1. Jak zauważyłem, CPython (przynajmniej w Linuksie) zachowuje się inaczej w zależności od tego, gdzie idzie wyjście. Jeśli przejdzie do tty, dane wyjściowe są opróżniane po każdym ' \n'
    Jeśli przejdzie do potoku / procesu, jest buforowany i można użyć flush()rozwiązań bazowych lub opcji -u zalecanej powyżej.

  2. Nieznacznie związane z buforowaniem danych wyjściowych:
    jeśli iterujesz po liniach wejścia za pomocą

    for line in sys.stdin:
    ...

wówczas za wdrażanie w CPython będzie zbierać dane wejściowe na chwilę, a następnie wykonać ciało pętli za bandą liniach wejściowych. Jeśli twój skrypt ma zamiar zapisać dane wyjściowe dla każdego wiersza wejściowego, może to wyglądać jak buforowanie danych wyjściowych, ale w rzeczywistości jest ono grupowe, a zatem żadna z flush()technik itp. Nie pomoże. Co ciekawe, nie masz takiego zachowania w pypy . Aby tego uniknąć, możesz użyć

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

tzp
źródło
oto twój komentarz . Może to być błąd w starszych wersjach języka Python. Czy możesz podać przykładowy kod? Coś jak for line in sys.stdinvs.for line in iter(sys.stdin.readline, "")
jfs,
dla linii w sys.stdin: print („Linia:” + linia); sys.stdout.flush ()
tzp
wygląda jak błąd odczytu z wyprzedzeniem . Powinno tak się zdarzyć tylko w Pythonie 2 i jeśli stdin jest potokiem. Kod w moim poprzednim komentarzu pokazuje problem ( for line in sys.stdinzapewnia opóźnioną odpowiedź)
jfs
2

Jednym ze sposobów uzyskania niebuforowanych danych wyjściowych byłoby użycie sys.stderrzamiast sys.stdoutlub po prostu wywołanie w sys.stdout.flush()celu jawnego wymuszenia zapisu.

Możesz łatwo przekierować wszystko wydrukowane, wykonując:

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

Lub przekierować tylko dla określonej printinstrukcji:

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

Aby zresetować standardowe wyjście, wystarczy:

sys.stdout = sys.__stdout__
stderr
źródło
1
Może to być bardzo mylące, gdy później spróbujesz przechwycić dane wyjściowe za pomocą standardowego przekierowania i okaże się, że nic nie wychwytujesz! ps twój stdout jest pogrubiony i takie tam.
Freespace
1
Jedną wielką ostrożnością przy selektywnym drukowaniu do stderr jest to, że powoduje to, że linie wydają się nie na miejscu, więc jeśli nie masz także znacznika czasu, może to być bardzo mylące.
haridsv