Перенаправить команду печати в python script через tqdm.write()
Я использую tqdm
в Python для отображения консоли-progressbars в наших скриптах.
Тем не менее, я должен вызывать функции, которые print
сообщения на консоль также и которые я не могу изменить.
В общем, запись на консоль при отображении индикаторов выполнения в консоли вызывает беспорядок на дисплее следующим образом:
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]
В соответствии с документацией tqdm
метод tqdm.write()
предоставляет средство для записи сообщений на консоль без нарушения отображаемых контрольных баров,
Таким образом, правильный вывод обеспечивается этим фрагментом:
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]
С другой стороны, существует это решение, которое позволяет отключить эти функции, довольно элегантно перенаправляя sys.stdout
в пустоту.
Это отлично работает для глушения функций.
Так как я хочу показывать сообщения из этих функций, не нарушая индикаторы выполнения, я попытался объединить оба решения в одно, перенаправив sys.stdout
в tqdm.write()
и, в свою очередь, разрешив tqdm.write()
записать в old 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()
должен управлять очисткой индикатора выполнения перед записью сообщения, а затем переписывать индикатор выполнения.
Что мне не хватает?
Ответы
Ответ 1
Переадресация 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
.
Ответ 2
Это может быть плохой способ, но я меняю встроенную функцию печати.
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
Ответ 3
Смешивая user493630 и gaborous ответы, я создал этот менеджер контекста, который не должен использовать параметр file=sys.stdout
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
Чтобы использовать его, просто:
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)
Ответ 4
ОП решение почти правильное. Вот тест в библиотеке tqdm, который испортил ваш вывод (https://github.com/tqdm/tqdm/blob/master/tqdm/_tqdm.py#L546-L549):
if hasattr(inst, "start_t") and (inst.fp == fp or all(
f in (sys.stdout, sys.stderr) for f in (fp, inst.
inst.clear(nolock=True)
inst_cleared.append(inst)
tqdm.write
проверяет файл, который вы предоставили, чтобы увидеть, есть ли риск столкновения между текстом для печати и потенциальными барами tqdm. В вашем случае stdout и stderr смешиваются в терминале, поэтому возникает коллизия. Чтобы противостоять этому, когда тест пройден, tqdm очищает столбцы, печатает текст и выводит столбцы обратно после.
Здесь тест fp == sys.stdout
завершается ошибкой, поскольку sys.stdout
стал DummyFile
, а fp является настоящим sys.stdout
, поэтому поведение при очистке не включено. Простой оператор равенства в DummyFile
исправляет все.
class DummyFile(object):
def __init__(self, file):
self.file = file
def write(self, x):
tqdm.write(x, end="", file=self.file)
def __eq__(self, other):
return other is self.file
Кроме того, поскольку print передает новую sys.stdout
в sys.stdout
(или не зависит от выбора пользователя), вы не хотите, чтобы tqdm самостоятельно добавлял другую, поэтому лучше установить опцию end=''
чем выполнять strip
на содержимое.
Преимущества этого решения
С громким ответом tqdm(..., file=sys.stdout)
загрязняет ваш поток вывода кусками бара. Сохраняя file=sys.stdout
(по умолчанию), вы разделяете свои потоки.
С ответами Conchylicultor и user493630 вы печатаете только патч. Однако другие системы, такие как ведение журнала, напрямую передаются в sys.stdout, поэтому они не проходят через tqdm.write
.