2016-05-02 4 views
8

Я использую tqdm в Python для отображения консольных баров в наших скриптах. Тем не менее, мне нужно вызвать функции, которые print сообщения на консоль также и которые я не могу изменить. В общем, писать на консоль во время отображения прогресса баров в консоли путает дисплей следующим образом:Передать команду печати в скрипте python через tqdm.write()

from time import sleep 
from tqdm import tqdm 

def blabla(): 
    print "Foo blabla" 

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

Это создает выход:

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 метод tqdm.write() предоставляет средства для записи сообщения на консоль, не нарушая отображаемые панели progressbars. Таким образом, правый выход обеспечивается посредством этого фрагмента:

from time import sleep 
from tqdm import tqdm 

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

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

И выглядит следующим образом:

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

С другой стороны, это solution which permits to silence those functions по вполне элегантно перенаправлять sys.stdout в пустоту. Это прекрасно работает для глушения функций.

Так как я хочу, чтобы отобразить сообщения из этих функций, тем не менее, не нарушая бары прогресса, я попытался объединить оба решения в один пути перенаправления sys.stdout к tqdm.write() и, в свою очередь, давая tqdm.write() записи в старогоsys.stdout. Это приводит фрагмент кода:

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) 

Однако, это на самом деле создает еще более перепутались вывод, как и раньше:

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: вызов tqdm.write(..., end="") внутри DummyFile.write() создает тот же результат, что и первый выход, который все еще испорчена.

Я не могу понять, почему это не сработает, так как tqdm.write() должен управлять очисткой индикатора выполнения перед записью сообщения, а затем переписывать индикатор выполнения.

Что мне не хватает?

ответ

9

Перенаправление sys.stdout всегда сложно, и это становится кошмаром, когда одновременно используются два приложения.

Здесь уловка заключается в том, что tqdm по умолчанию печатает до sys.stderr, а не sys.stdout. Обычно tqdm имеет стратегию борьбы с микшированием для этих двух специальных каналов, но поскольку вы перенаправляете sys.stdout, tqdm путается, потому что изменяется дескриптор файла.

Таким образом, вам просто нужно явно указать file=sys.stdout к tqdm и он будет работать:

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

Я также добавил еще несколько уловок, чтобы сделать выход приятнее (например, не бесполезно \n, когда не используя print() без end='').

/EDIT: на самом деле кажется, что вы можете сделать stdout Перенаправление после запуска tqdm, вам просто нужно указать dynamic_ncols=True в tqdm.

+1

Если 'BlaBla()' рейз ошибка, стандартный stdout никогда не будет восстановлен. – Conchylicultor

5

Это может быть плохой способ, но я меняю встроенную функцию печати.

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

Нет, это не плохо, очень интересный подход. Спасибо, что зарегистрировались и разместили его! – gaborous

+0

AFAIK это решение в Ptyhon 3.x, но не в Python 2.6/2.7. –

+0

Я считаю, что это не работает с автоматической перезагрузкой IPython - если я отредактирую какую-либо часть исходного файла, содержащего 'inspect.builtins.print = new_print', тогда я получу segfault в ipython при следующем запуске любого код из этого файла. – user2561747

1

При смешивании, user493630 и gaborous ответов, я создал этот контекст менеджер, который избежать необходимости использовать параметр tqdmfile=sys.stdout.

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 

Чтобы использовать его, просто:

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

Чтобы еще больше упростить, можно обернуть код в новой функции:

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)