Python进度条通过logging模块

19

我看过不同的Python进度条解决方案,但简单的stdout解决方案对我的项目无效。我有多个类,并使用“logging”模块将信息输出到stdout。我有一个函数,想要在一行上显示进度条,每次刷新缓冲区。

以下是简单进度条的示例:

for i in range(100):
    time.sleep(1)
    sys.stdout.write("\r%d%%" %i)
    sys.stdout.flush()

当我尝试通过stdout写入并刷新缓冲区时,要么缓冲区没有被刷新,要么进度不会前进。我希望避免使用某种线程或复杂的过程来实现这一点。有人有喜欢的方法来实现这个吗?

6个回答

30

我找不到好的解决办法,所以我写了enlighten进度条来处理它。基本上,它改变了终端的滚动区域,因此记录日志在进度条之上而不是每次想要写入STDOUT时都必须重新绘制进度条。这样你可以随意写入终端,而无需修改loggingprint等内容。

import logging
import time
import enlighten

# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger()

# Setup progress bar
manager = enlighten.get_manager()
pbar = manager.counter(total=100, desc='Ticks', unit='ticks')

for i in range(1, 101):
    logger.info("Processing step %s" % i)
    time.sleep(.2)
    pbar.update()

3
我刚刚发现了这个模块,正是我所需要的!感谢你提供这个。 - Alejandro Piad
在1.3.0版本中添加了Windows支持。 - aviso
版本1.10.0增加了对Jupyter Notebooks的支持。 - aviso

18
我是这样解决的:
import logging
import time
from tqdm import tqdm
import io

class TqdmToLogger(io.StringIO):
    """
        Output stream for TQDM which will output to logger module instead of
        the StdOut.
    """
    logger = None
    level = None
    buf = ''
    def __init__(self,logger,level=None):
        super(TqdmToLogger, self).__init__()
        self.logger = logger
        self.level = level or logging.INFO
    def write(self,buf):
        self.buf = buf.strip('\r\n\t ')
    def flush(self):
        self.logger.log(self.level, self.buf)

if __name__ == "__main__":
    logging.basicConfig(format='%(asctime)s [%(levelname)-8s] %(message)s')
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)

    tqdm_out = TqdmToLogger(logger,level=logging.INFO)
    for x in tqdm(range(100),file=tqdm_out,mininterval=30,):
        time.sleep(.5)

输出

2016-12-19 15:35:06 [INFO    ] 16%|#####9                                | 768/4928 [07:04<40:50,  1.70it/s]
2016-12-19 15:36:07 [INFO    ] 18%|######6                               | 865/4928 [08:04<40:34,  1.67it/s]

只是好奇进度条末尾的数字#####9是从哪里来的,我们怎样才能去掉它? - Sam
1
这很好,但每次更新它仍然会打印一个新行,是吗? - slhck
所有数字都是“十进制数”。实际上,记录器始终会在新行上打印。如果您要记录到文件中,则这很好。如果您不想要一个带有时间戳的新行,则不需要使用记录器。 - ddofborg
我喜欢这个解决方案将tqdm的输出通过Python日志记录器发送出去,这正是我想要的。我发现与tqdm和日志记录相关的大多数问题/解决方案都是将所有其他日志记录通过tqdm的输出处理程序发送出去。 - undefined

6

您可以使用tqdm进度条,并通过日志记录的自定义处理程序实现与此处描述的日志记录不冲突的tqdm写入功能

import logging
import time
import colorlog
from tqdm import tqdm

class TqdmHandler(logging.StreamHandler):
    def __init__(self):
        logging.StreamHandler.__init__(self)

    def emit(self, record):
        msg = self.format(record)
        tqdm.write(msg)

if __name__ == "__main__":
    for x in tqdm(range(100)):
        logger = colorlog.getLogger("MYAPP")
        logger.setLevel(logging.DEBUG)
        handler = TqdmHandler()
        handler.setFormatter(colorlog.ColoredFormatter(
            '%(log_color)s%(name)s | %(asctime)s | %(levelname)s | %(message)s',
            datefmt='%Y-%d-%d %H:%M:%S',
            log_colors={
                'DEBUG': 'cyan',
                'INFO': 'white',
                'SUCCESS:': 'green',
                'WARNING': 'yellow',
                'ERROR': 'red',
                'CRITICAL': 'red,bg_white'},))

        logger.addHandler(handler)
        logger.debug("Inside subtask: "+str(x))
        time.sleep(.5)

对我来说不起作用。我认为你可以尝试这个链接: https://gist.github.com/w495/80f6a7351a10d5b6cecb5dad6c8cd8d6 - Ilia w495 Nikitin
日期格式 datefmt='%Y-%d-%d %H:%M:%S' 是否应该改为 datefmt='%Y-%m-%d %H:%M:%S'(月份)? - Pascal

3
如果您知道进度条总是要写到STDOUT中,应该使用print而不是logger。请参阅Python日志记录教程文档中的文档

使用“print”的问题在于它会将每个值放在新行上,不再使输出成为进度条。STDOUT 应该刷新缓冲区并覆盖现有行,但是当在同时记录日志的代码中使用它时,似乎无法正常工作。请注意,我没有在进度条代码周围包装任何日志模块语法。 - Famous Food Finder
1
使用您上面发布的确切代码,我看到输出被替换在原地。您是否看到每个数字都在新行上? - jeffknupp
这是否回答了问题? - Luk Aron

1
你可能想要使用 progressbar2,它可以在打印进度条的同时保持日志记录:
最简示例:
import time
import progressbar

for i in progressbar.progressbar(range(100), redirect_stdout=True):
    print('Some text', i)
    time.sleep(0.1)

这将产生如下结果: Illustration of result

(progressbar2 兼容 Python2 和 Python3)


0

清理这些提案的代码,我认为这是正确的实现方式,除了tqdm(也在这里发布)之外没有其他外部依赖项:

import logging
from tqdm import tqdm


class TqdmLoggingHandler(logging.StreamHandler):
    """Avoid tqdm progress bar interruption by logger's output to console"""
    # see logging.StreamHandler.eval method:
    # https://github.com/python/cpython/blob/d2e2534751fd675c4d5d3adc208bf4fc984da7bf/Lib/logging/__init__.py#L1082-L1091
    # and tqdm.write method:
    # https://github.com/tqdm/tqdm/blob/f86104a1f30c38e6f80bfd8fb16d5fcde1e7749f/tqdm/std.py#L614-L620

    def emit(self, record):
        try:
            msg = self.format(record)
            tqdm.write(msg, end=self.terminator)
        except RecursionError:
            raise
        except Exception:
            self.handleError(record)

测试:

import time

log = logging.getLogger(__name__)
log.setLevel(logging.INFO)
log.addHandler(TqdmLoggingHandler())
#   ^-- Assumes this will be the unique handler emitting messages to sys.stdout.
#       If other handlers output to sys.stdout (without tqdm.write),
#       progress bar will be interrupted by those outputs

for i in tqdm(range(20)):
    log.info(f"Looping {i}")
    time.sleep(0.1)

对我没用 :( - Progressify

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接