38

tqdm在 Python 中使用在我们的脚本中显示控制台进度条。但是,我还必须调用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() 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()会产生与仍然混乱的第一个输出相同的结果。

我不明白为什么这不起作用,因为tqdm.write()应该在编写消息之前管理清除进度条,然后重写进度条。

我错过了什么?

4

4 回答 4

32

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

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

因此,您只需要明确指定file=sys.stdouttotqdm就可以了:

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()without时没有用处end='')。

/EDIT:其实好像你可以stdout在starting之后做重定向tqdm,你只需要dynamic_ncols=Truetqdm.

于 2016-05-15T20:15:18.477 回答
12

这可能是不好的方式,但我改变了内置的打印功能。

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
于 2016-07-13T08:08:46.600 回答
11

通过混合 user493630 和大量答案,我创建了这个上下文管理器,避免了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)
于 2017-02-23T19:49:36.027 回答
1

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.stdoutbecome 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(或不取决于用户选择),您不希望 tqdm 自己添加另一个,因此设置选项end=''比执行strip内容更好。

此解决方案的优点

用 gaborous 的回答,用条形图tqdm(..., file=sys.stdout)污染你的输出流。通过保留file=sys.stdout(默认),您可以将流分开。
使用 Conchylicultor 和 user493630 的答案,您只需进行补丁打印。但是其他系统(例如日志记录)直接流式传输到 sys.stdout,因此它们不会通过tqdm.write.

于 2019-01-26T00:10:48.813 回答