2016-05-02 3 views
8

Ich benutze tqdm in Python Console-Fortschrittsbalken in unseren Skripts anzuzeigen. Allerdings muss ich Funktionen aufrufen, die print Nachrichten an die Konsole auch und die ich nicht ändern kann. Im Allgemeinen an die Konsole zu schreiben, während in den Konsolenfortschrittsbalken anzeigt verunstaltet das Display wie so up:Redirect Druckbefehl in Python-Skript durch tqdm.write()

0%|           | 0/3 [00:00<?, ?it/s]Foo 
blabla 
33%|###########6      | 1/3 [00:00<00:01, 2.00it/s]Foo 
blabla 
67%|#######################3   | 2/3 [00:01<00:00, 2.00it/s]Foo 
blabla 
100%|###################################| 3/3 [00:01<00:00, 2.00it/s] 

According to the documentation of tqdm Verfahren tqdm.write() stellen ein Mittel zu schreiben:

from time import sleep 
from tqdm import tqdm 

def blabla(): 
    print "Foo blabla" 

for k in tqdm(range(3)): 
    blabla() 
    sleep(.5) 

Diese den Ausgang schafft Nachrichten an die Konsole, ohne die angezeigten Fortschrittsbalken zu unterbrechen. Somit wird der rechte Ausgang dieses Schnipsel zur Verfügung gestellt:

from time import sleep 
from tqdm import tqdm 

def blabla(): 
    tqdm.write("Foo blabla") 

for k in tqdm(range(3)): 
    blabla() 
    sleep(.5) 

Und das sieht so aus:

Foo blabla 
Foo blabla 
Foo blabla 
100%|###################################| 3/3 [00:01<00:00, 1.99it/s] 

Auf der anderen Seite gibt es diese solution which permits to silence those functions durch ganz elegant sys.stdout ins Leere umgeleitet wird. Dies funktioniert perfekt zum Stummschalten der Funktionen.

Da ich will, ohne zu brechen, die Fortschrittsbalken dennoch die Nachrichten von diesen Funktionen anzuzeigen, habe ich versucht, die beiden Lösungen in eine verschmelzen durch sys.stdout zu tqdm.write() und wiederum ließ tqdm.write() Schreiben in die altensys.stdout umleitet. Daraus ergibt sich die Schnipsel:

from time import sleep 

import contextlib 
import sys 

from tqdm import tqdm 

class DummyFile(object): 
    file = None 
    def __init__(self, file): 
    self.file = file 

    def write(self, x): 
    tqdm.write(x, file=self.file) 

@contextlib.contextmanager 
def nostdout(): 
    save_stdout = sys.stdout 
    sys.stdout = DummyFile(save_stdout) 
    yield 
    sys.stdout = save_stdout 

def blabla(): 
    print "Foo blabla" 

for k in tqdm(range(3)): 
    with nostdout(): 
    blabla() 
    sleep(.5) 

Dies ist jedoch tatsächlich schafft eine noch verkorkste Ausgang nach wie vor:

0%|           | 0/3 [00:00<?, ?it/s]Foo 
blabla 


33%|###########6      | 1/3 [00:00<00:01, 2.00it/s]Foo 
blabla 


67%|#######################3   | 2/3 [00:01<00:00, 2.00it/s]Foo 
blabla 


100%|###################################| 3/3 [00:01<00:00, 2.00it/s] 

FYI: Aufruf tqdm.write(..., end="") innerhalb DummyFile.write() erzeugt das gleiche Ergebnis wie die erste Ausgabe, die ist immer noch vermasselt.

Ich kann nicht verstehen, warum das nicht funktionieren würde, da tqdm.write() verwaltet werden soll, den Fortschrittsbalken vor dem Schreiben der Nachricht zu löschen und dann den Fortschrittsbalken neu schreiben.

Was fehlt mir?

Antwort

9

Die Umleitung sys.stdout ist immer knifflig, und es wird zum Albtraum, wenn zwei Anwendungen gleichzeitig damit spielen.

Hier ist der Trick, dass tqdm standardmäßig auf sys.stderr, nicht sys.stdout druckt. Normalerweise hat tqdm eine Anti-Verwechslungs-Strategie für diese zwei speziellen Kanäle, aber da Sie sys.stdout umleiten, wird tqdm verwirrt, weil sich das Datei-Handle ändert.

So müssen Sie nur explizit file=sys.stdout zu tqdm angeben und es wird funktionieren:

from time import sleep 

import contextlib 
import sys 

from tqdm import tqdm 

class DummyFile(object): 
    file = None 
    def __init__(self, file): 
    self.file = file 

    def write(self, x): 
    # Avoid print() second call (useless \n) 
    if len(x.rstrip()) > 0: 
     tqdm.write(x, file=self.file) 

@contextlib.contextmanager 
def nostdout(): 
    save_stdout = sys.stdout 
    sys.stdout = DummyFile(sys.stdout) 
    yield 
    sys.stdout = save_stdout 

def blabla(): 
    print("Foo blabla") 

# tqdm call to sys.stdout must be done BEFORE stdout redirection 
# and you need to specify sys.stdout, not sys.stderr (default) 
for _ in tqdm(range(3), file=sys.stdout): 
    with nostdout(): 
     blabla() 
     sleep(.5) 

print('Done!') 

ich auch noch ein paar Tricks hinzugefügt, um den Ausgang schöner zu machen (zB keine nutzlosen \n wenn print() ohne end='' mit).

/EDIT: In der Tat scheint es, können Sie nach dem Start tqdm die stdout Umleitung tun, brauchen Sie nur dynamic_ncols=True in tqdm angeben.

+1

Wenn 'blabla()' raise ein Fehler, der Standard-Standard wird nie wiederhergestellt werden. – Conchylicultor

5

Es könnte der schlechte Weg sein, aber ich ändere die integrierte Druckfunktion.

import inspect 
import tqdm 
# store builtin print 
old_print = print 
def new_print(*args, **kwargs): 
    # if tqdm.tqdm.write raises error, use builtin print 
    try: 
     tqdm.tqdm.write(*args, **kwargs) 
    except: 
     old_print(*args, ** kwargs) 
# globaly replace print with new_print 
inspect.builtins.print = new_print 
+1

Nein, das ist gar nicht schlecht, sehr interessanter Ansatz. Vielen Dank für die Registrierung und Veröffentlichung! – gaborous

+0

AFAIK das ist die Lösung in Ptyhon 3.x, aber nicht in Python 2.6/2.7. –

+0

Ich finde das funktioniert nicht gut mit IPython automatisch neu laden - wenn ich einen Teil der Quelldatei bearbeiten, die enthält 'inspect.builtins.print = new_print' dann bekomme ich einen segfault in ipython das nächste Mal, wenn ich versuche, eine zu starten Code aus dieser Datei. – user2561747

1

Durch Mischen, user493630 und gaborous Antworten, habe ich diesen Kontext-Manager, den die file=sys.stdout Parameter von tqdm verwenden vermeiden müssen.

import inspect 
import contextlib 
import tqdm 

@contextlib.contextmanager 
def redirect_to_tqdm(): 
    # Store builtin print 
    old_print = print 
    def new_print(*args, **kwargs): 
     # If tqdm.tqdm.write raises error, use builtin print 
     try: 
      tqdm.tqdm.write(*args, **kwargs) 
     except: 
      old_print(*args, ** kwargs) 

    try: 
     # Globaly replace print with new_print 
     inspect.builtins.print = new_print 
     yield 
    finally: 
     inspect.builtins.print = old_print 

es zu benutzen, einfach:

for i in tqdm.tqdm(range(100)): 
    with redirect_to_tqdm(): 
     time.sleep(.1) 
     print(i) 

noch weiter zu vereinfachen, ist es möglich, den Code in einer neuen Funktion zu wickeln:

def tqdm_redirect(*args, **kwargs): 
    with redirect_to_tqdm(): 
     for x in tqdm.tqdm(*args, **kwargs): 
      yield x 

for i in tqdm_redirect(range(20)): 
    time.sleep(.1) 
    print(i)