通过tqdm.write()

时间:2016-05-02 16:14:41

标签: python python-2.7 tqdm

我在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()应该在编写消息之前管理清除进度条,然后重写进度条。

我错过了什么?

4 个答案:

答案 0 :(得分:14)

重定向sys.stdout总是很棘手,当两个应用程序同时使用它时,它变成了一场噩梦。

这里的诀窍是tqdm默认情况下打印到sys.stderr,而不是sys.stdout。通常,tqdm对这两个特殊渠道都有一个反混淆策略,但由于您重定向sys.stdouttqdm会因为文件句柄发生变化而感到困惑。

因此,您只需要明确指定file=sys.stdouttqdm,它就会起作用:

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