我在Python中使用tqdm
在我们的脚本中显示控制台进度栏。
但是,我必须将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]
According to the documentation of 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]
另一方面,有solution which permits to silence those functions这个plunker非常优雅地将sys.stdout
重定向到虚空中。
这非常适合于消除功能。
由于我想在不破坏进度条的情况下显示来自这些函数的消息,我尝试通过将sys.stdout
重定向到tqdm.write()
将两个解决方案合并为一个,然后让{{1写入旧 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]
内调用tqdm.write(..., end="")
会产生与仍然混乱的第一个输出相同的结果。
我无法理解为什么这不起作用,因为DummyFile.write()
应该在编写消息之前管理清除进度条,然后重写进度条。
我错过了什么?
答案 0 :(得分:14)
重定向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=''
。
/编辑:事实上,您似乎可以在stdout
开始后进行tqdm
重定向,只需在dynamic_ncols=True
中指定tqdm
。
答案 1 :(得分:6)
这可能是坏方法,但我更改了内置打印功能。
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
答案 2 :(得分:2)
通过混合,user493630和gaborous答案,我创建了这个上下文管理器,避免使用file=sys.stdout
的{{1}}参数。
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)
答案 3 :(得分:0)
OP的解决方案几乎是正确的。 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
此外,由于打印将换行符传递到sys.stdout
(或不取决于用户选择),因此您不希望tqdm自己添加另一个,因此最好设置选项{{1 }},而不是对内容执行end=''
。
凭异的回答,strip
用条形图污染了您的输出流。通过保留tqdm(..., file=sys.stdout)
(默认),可以使流分开。
有了Conchylicultor和user493630的回答,您才可以打补丁打印。但是,其他系统(例如日志记录)直接流式传输到sys.stdout,因此它们不会通过file=sys.stdout
。