通过tqdm.write()在python脚本中重定向打印命令

Pie*_*der 13 python python-2.7 tqdm

我正在使用tqdmPython在我们的脚本中显示控制台进度条.但是,我必须将调用print消息的函数调用到控制台,而且我无法更改.通常,在控制台中显示进度条的同时写入控制台会使显示屏变得如此:

from time import sleep
from tqdm import tqdm

def blabla():
  print "Foo blabla"

for k in tqdm(range(3)):
  blabla()
  sleep(.5)
Run Code Online (Sandbox Code Playgroud)

这会创建输出:

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]
Run Code Online (Sandbox Code Playgroud)

根据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)
Run Code Online (Sandbox Code Playgroud)

看起来像这样:

Foo blabla
Foo blabla
Foo blabla
100%|###################################| 3/3 [00:01<00:00,  1.99it/s]
Run Code Online (Sandbox Code Playgroud)

另一方面,有这种解决方案允许通过非常优雅地重定向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)
Run Code Online (Sandbox Code Playgroud)

但是,这实际上会像以前一样创建一个更加混乱的输出:

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]
Run Code Online (Sandbox Code Playgroud)

仅供参考:调用tqdm.write(..., end="")内部DummyFile.write()会产生与第一个仍然混乱的输出相同的结果.

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

我错过了什么?

gab*_*ous 17

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

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

因此,你只需要明确指定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!')
Run Code Online (Sandbox Code Playgroud)

我还增加了一些技巧,让输出更好的(例如,没有无用的\n使用时print()end='').

/编辑:其实好像你可以做stdout启动后重定向tqdm,您只需要指定dynamic_ncols=Truetqdm.

  • 如果 `blabla()` 引发错误,则永远不会恢复标准 stdout。 (2认同)

Con*_*tor 9

通过混合,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
Run Code Online (Sandbox Code Playgroud)

要使用它,只需:

for i in tqdm.tqdm(range(100)):
    with redirect_to_tqdm():
        time.sleep(.1)
        print(i)
Run Code Online (Sandbox Code Playgroud)

为了进一步简化,可以将代码包装在一个新函数中:

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)
Run Code Online (Sandbox Code Playgroud)


小智 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
Run Code Online (Sandbox Code Playgroud)

  • 我对覆盖库函数了解不够,但我确信使用不合格的“例外”语句是一种糟糕的做法,原因有很多 - 例如,它实际上捕获了任何异常,包括键盘中断!最好弄清楚可能抛出哪些异常并专门捕获它们。 (2认同)