在Python程序中使用doctest和日志记录

7 投票
3 回答
1151 浏览
提问于 2025-04-15 22:03

#!/usr/bin/python2.4
import logging
import sys
import doctest
def foo(x):
        """
    >>> foo (0)
    0
        """
        print ("%d" %(x))
        _logger.debug("%d" %(x))
def _test():
        doctest.testmod()
_logger = logging.getLogger()
_logger.setLevel(logging.DEBUG)
_formatter = logging.Formatter('%(message)s')
_handler = logging.StreamHandler(sys.stdout)
_handler.setFormatter(_formatter)
_logger.addHandler(_handler)
_test()

我想在所有的打印语句中使用日志模块。我查了前50个谷歌搜索结果,发现大家都说doctest用的是自己的一份标准输出(stdout)。如果用print语句,它能正常工作;但如果用logger,它会记录到根控制台。有没有人能给我一个可以工作的示例代码,让我能把这两者结合起来?另外,运行nose来测试doctest时,日志输出会在测试结束时追加到最后(前提是你设置了开关),它并不会把这些当作打印语句来处理。

3 个回答

1

有一种简单且通用的方法是这样的:

import sys
import logging

LOGGER = logging.getLogger(__name__)
if hasattr(sys.modules['__main__'], '_SpoofOut'):
    LOGGER.setLevel(logging.DEBUG)
    LOGGER.addHandler(logging.StreamHandler())

_SpoofOut这个属性是由doctest模块添加的。如果这个属性存在,你就可以专门为doctest设置日志记录。例如,在我的例子中,我设置了详细的调试模式,并将日志输出到控制台。

4

在你的doctest中,在需要记录日志之前,先对你的日志记录器做一个 addHandler(logging.StreamHandler(sys.stdout)) 的操作。

举个例子,假设 logger 是你的日志对象:

"""
This is a doctest that will capture output from the logging module.

>>> logger.addHandler(logging.StreamHandler(sys.stdout))

The rest of your doctest, now able to use output from the logging
module...
"""

解释:正如你所说,“doctest使用了它自己的一份stdout”。它通过伪装 sys.stdout 来实现这一点:它把 sys.stdout 设置为 DocTestRunner._fakeout。一旦doctest开始运行,doctest就会用它的伪装替代真实的 sys.stdout。如果你在这个时候为sys.stdout创建一个 logging.StreamHandler,那么sys.stdout将指向doctest的伪装,而不是实际的sys.stdout,这样你就实际上是在为doctest的伪装添加一个StreamHandler,而不是为真实的sys.stdout添加,这样doctest就能看到你记录的输出了。

这个解决方案还有一个好处,就是不依赖于doctest内部的私有变量,比如 _fakeout_SpoofOut,以防它们将来发生变化。

注意事项:如果你看到

error: [Errno 128] Transport endpoint is not connected

你可能忘记了 import sys

1

我不太明白你为什么想这么做,但如果你真的需要这样做,你可以自己定义一个 DocTestRunner 的子类,并重写它的 run 方法:

#imports left out, code not tested - just to point you in the right direction
class MyDocTestRunner(DocTestRunner):
    def run(self, test, compileflags=None, out=None, clear_globs=True):
        if out is None:
            handler = None
        else:
            handler = StreamHandler(out)
        logger = logging.getLogger() # root logger (say)
        if handler:
            logger.addHandler(handler)
        try:
            DocTestRunner.run(self, test, compileflags, out, clear_globs)
        finally:
            if handler:
                logger.removeHandler(handler)
                handler.close()

然后用这个新的运行器来替代 DocTestRunner

撰写回答