2016-05-02 23 views
8

Sto usando tqdm in Python per visualizzare le barre di avanzamento della console nei nostri script. Tuttavia, devo chiamare le funzioni che i messaggi print alla console e che non posso cambiare. In generale, la scrittura alla console durante la visualizzazione di barre di avanzamento nella console scombina il display in questo modo:Comando di reindirizzamento della stampa in script python tramite tqdm.write()

from time import sleep 
from tqdm import tqdm 

def blabla(): 
    print "Foo blabla" 

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

Questo crea l'output:

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 il metodo tqdm.write() fornisce un mezzo per scrivere messaggi alla console senza interrompere i progressbar visualizzati. Così, l'uscita giusta è fornita da questo frammento:

from time import sleep 
from tqdm import tqdm 

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

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

E assomiglia a questo:

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

D'altra parte, c'è questa solution which permits to silence those functions da molto elegantemente reindirizzando sys.stdout nel vuoto. Questo funziona perfettamente per mettere a tacere le funzioni.

Dal momento che voglio per visualizzare i messaggi da queste funzioni comunque senza rompere le barre di avanzamento, ho cercato di unire le due soluzioni in un unico reindirizzando sys.stdout a tqdm.write() e, a sua volta, lasciando tqdm.write() scrittura alla vecchiosys.stdout. Il risultato è il frammento:

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) 

Tuttavia, questo crea effettivamente una potenza ancora più incasinato di prima:

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: chiamando tqdm.write(..., end="") all'interno DummyFile.write() crea lo stesso risultato della prima uscita che è ancora incasinato.

Non riesco a capire perché questo non funzionerebbe, dal momento che tqdm.write() deve gestire la cancellazione della barra di avanzamento prima di scrivere il messaggio e quindi riscrivere la barra di avanzamento.

Cosa mi manca?

risposta

9

Il reindirizzamento sys.stdout è sempre complicato e diventa un incubo quando due applicazioni stanno giocando con esso allo stesso tempo.

Qui il trucco è che tqdm di default stampa su sys.stderr, non su sys.stdout. Normalmente, tqdm ha una strategia anti-mixaggio per questi due canali speciali, ma poiché si sta reindirizzando sys.stdout, tqdm viene confuso perché l'handle del file cambia.

Pertanto, è solo bisogno di specificare esplicitamente file=sys.stdout per tqdm e funzionerà:

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!') 

Ho anche aggiunto un paio di trucchi per rendere il più piacevole uscita (ad esempio, senza inutili \n quando si utilizza print() senza end='').

/EDIT: infatti sembra che si può fare il reindirizzamento stdout dopo l'avvio tqdm, basta specificare dynamic_ncols=True in tqdm.

+1

Se 'blabla()' rilancio un errore, lo stdout standard non verrà mai ripristinato. – Conchylicultor

5

Potrebbe essere il modo sbagliato, ma cambio la funzione di stampa integrata.

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

No, non è affatto male, approccio molto interessante. Grazie per esserti registrato e pubblicato! – gaborous

+0

AFAIK questa è la soluzione in Ptyhon 3.x, ma non in Python 2.6/2.7. –

+0

Trovo che questo non funzioni bene con IPython auto-reload - se modifico qualsiasi parte del file sorgente che contiene 'inspect.builtins.print = new_print' allora ottengo un segfault in ipython la prossima volta che cerco di eseguire qualsiasi codice da quel file. – user2561747

1

Mescolando, user493630 e risposte gaborous, ho creato questo manager contesto che evita di dover utilizzare il parametro file=sys.stdout di tqdm.

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 

Per utilizzarlo, è sufficiente:

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

Per semplificare ancora di più, è possibile inserire il codice in una nuova funzione:

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)