【问题标题】:Redirect print command in python script through tqdm.write()通过 tqdm.write() 在 python 脚本中重定向打印命令
【发布时间】:2016-08-27 11:35:46
【问题描述】:

我在 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 tqdmtqdm.write() 方法提供了一种在不破坏显示进度条的情况下将消息写入控制台的方法。 因此,这个 sn-p 提供了正确的输出:

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 非常优雅地将sys.stdout 重定向到虚空中。 这对于使函数静音非常有效。

由于我想在不破坏进度条的情况下显示来自这些函数的消息,我尝试通过将sys.stdout 重定向到tqdm.write() 并反过来让tqdm.write() 写入旧 sys.stdout。 这导致 sn-p:

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]

仅供参考:在 DummyFile.write() 内调用 tqdm.write(..., end="") 会创建与第一个输出相同的结果,但仍然是混乱的。

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

我错过了什么?

【问题讨论】:

    标签: python python-2.7 tqdm


    【解决方案1】:

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

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

    因此,您只需将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!')
    

    我还添加了一些技巧以使输出更好(例如,在使用 print() 而不使用 end='' 时没有无用的 \n)。

    /EDIT:其实你好像可以在启动tqdm之后做stdout重定向,你只需要在tqdm中指定dynamic_ncols=True

    【讨论】:

    • 如果blabla() 引发错误,标准的stdout 将永远无法恢复。
    【解决方案2】:

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

    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
    

    【讨论】:

    • 不,这还不错,非常有趣的方法。感谢您注册并发布!
    • AFAIK 这是 Ptyhon 3.x 中的解决方案,但不是 Python 2.6/2.7。
    • 我发现这不适用于 IPython 自动重新加载 - 如果我编辑包含 inspect.builtins.print = new_print 的源文件的任何部分,那么我下次尝试运行时会在 ipython 中出现段错误该文件中的任何代码。
    • 我对覆盖库函数知之甚少,但我确信使用不合格的“except”语句是一种糟糕的做法,原因有很多——例如,它可以捕获任何异常,包括键盘中断!最好弄清楚可能会抛出哪些异常并专门捕获这些异常。
    【解决方案3】:

    通过混合 user493630 和大量答案,我创建了这个上下文管理器,它避免了使用 tqdmfile=sys.stdout 参数。

    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)
    

    【讨论】:

      【解决方案4】:

      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
      

      此外,由于 print 将换行符传递给 sys.stdout(或不取决于用户的选择),您不希望 tqdm 自己添加另一个,因此最好设置选项 end='' 而不是执行strip 内容。

      此解决方案的优点

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-04
        • 2015-06-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-11
        相关资源
        最近更新 更多