Python中的条件调试语句

14 投票
4 回答
5520 浏览
提问于 2025-04-17 03:34

在Python中,有几种方法可以打印“跟踪”输出。你可以使用 printimport loggingstdout.write 来打印调试信息,但它们都有一个缺点:即使日志记录器的阈值设置得太高,或者输出流已经关闭,Python仍然会计算打印语句的参数。(严格求值)这可能会浪费时间去处理字符串格式等。

一个明显的解决办法是把生成字符串的代码放到一个lambda表达式里,然后用我们自己的日志记录函数来有条件地调用这个lambda(这个函数会检查 __debug__ 这个内置变量,当你用 -O 启动Python进行优化时,它会被设置为False):

def debug(f):
  if __debug__:
    print f()
    #stdout.write(f())
    #logging.debug(f())

for currentItem in allItems:
  debug(lambda:"Working on {0}".format(currentItem))

这样做的好处是在发布版本中不会调用 str(currentItem)string.format,缺点是每次写日志时都得输入 lambda:

Python的 assert 语句在Python编译器中是特殊处理的。如果用 -O 启动Python,那么任何assert语句都会被丢弃,不会被计算。你可以利用这一点来创建另一个有条件评估的日志记录语句:

assert(logging.debug("Working on {0}".format(currentItem)) or True)

当用 -O 启动Python时,这行代码不会被计算。

甚至可以使用短路运算符 'and' 和 'or':

__debug__ and logging.debug("Working on {0}".format(currentItem));

不过现在我们得写28个字符,加上输出字符串的代码。

我想问的是:有没有什么标准的Python语句或函数,具有和 assert 语句一样的条件评估特性?或者,有没有人有其他替代这里提到的方法?

4 个回答

1

你可以使用 eval 这个方法:

import inspect

def debug(source):
  if __debug__:
    callers_locals = inspect.currentframe().f_back.f_locals
    print eval(source, globals(),  callers_locals)

for currentItem in ('a', 'b'):
  debug('"Working on {0}".format(currentItem)')
4

如果你所有的调试函数只需要一个字符串,那为什么不把它改成接收一个格式化字符串和一些参数呢:

debug(lambda:"Working on {0}".format(currentItem))

就变成了

debug("Working on {0}", currentItem)

而且

if __debug__:
    def debug(format, *values):
        print format.format(*values)
else:
    def debug(format, *values): pass

这样做有了你第一种选择的所有优点,而且不需要使用一个额外的lambda函数。如果把if __debug__:放到函数外面,这样在加载模块时只会测试一次,那么这个语句的开销就只是一条函数调用。

3

我想知道,当没有处理程序时,调用 logging.debug 会对性能产生多大影响。

不过,if __debug__: 这个语句只会被评估一次,即使是在函数的内部。

$ python -O
Python 2.6.6 (r266:84292, Dec 26 2010, 22:31:48)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import dis
>>> import logging
>>> def debug(*a, **kw):
...  if __debug__:
...   logging.debug(*a, **kw)
... 
>>> dis.dis(debug)
  2           0 LOAD_CONST               0 (None)
              3 RETURN_VALUE        
>>> 

而且,记录器可以使用字符串格式化操作符来帮你格式化消息。这里有一个稍微修改过的例子,来自于 logging.debug 文档

FORMAT = '%(asctime)-15s %(clientip)s %(user)-8s %(message)s'
logging.basicConfig(format=FORMAT)
d = { 'clientip' : '192.168.0.1', 'user' : 'fbloggs' }
debug('Protocol problem: %s', 'connection reset', extra=d)

在这种情况下,如果优化没有开启,消息字符串是不会被计算的。

撰写回答