Redirect print command in python script through tqdm.write()
Redirecting sys.stdout
is always tricky, and it becomes a nightmare when two applications are twiddling with it at the same time.
Here the trick is that tqdm
by default prints to sys.stderr
, not sys.stdout
. Normally, tqdm
has an anti-mixup strategy for these two special channels, but since you are redirecting sys.stdout
, tqdm
gets confused because the file handle changes.
Thus, you just need to explicitly specify file=sys.stdout
to tqdm
and it will work:
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!')
I also added a few more tricks to make the output nicer (eg, no useless \n
when using print()
without end=''
).
/EDIT: in fact it seems you can do the stdout
redirection after starting tqdm
, you just need to specify dynamic_ncols=True
in tqdm
.
It might be the bad way, but I change built-in print function.
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
By mixing, user493630 and gaborous answers, I created this context manager which avoid having to use the file=sys.stdout
parameter of 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
To use it, simply:
for i in tqdm.tqdm(range(100)):
with redirect_to_tqdm():
time.sleep(.1)
print(i)
To simplify even further, it is possible to wrap the code in a new function:
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)