【问题标题】:Conditionally evaluated debug statements in PythonPython 中条件评估的调试语句
【发布时间】:2011-11-29 20:55:39
【问题描述】:

Python 有几种打印“跟踪”输出的方法。 print, import logging, stdout.write 可用于打印调试信息,但它们都有一个缺点:即使记录器的阈值过高或流已关闭,Python 仍会评估打印语句的参数. (严格评估)这可能会花费字符串格式或更多。

明显的解决方法是将创建字符串的代码放入 lambda,并使用我们自己的日志记录函数有条件地调用 lambda(这个检查 __debug__ 内置变量,每当 python 启动时,该变量设置为 False -O 用于优化):

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 编译器特殊处理。如果 python 使用-O 运行,则任何断言语句都将被丢弃而不进行任何评估。您可以利用它来创建另一个条件评估的日志记录语句:

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

当 Python 以 -O 启动时,不会计算此行。

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

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

但现在我们最多有 28 个字符加上输出字符串的代码。

我要回答的问题是:是否有任何标准的 Python 语句或函数具有与 assert 语句相同的条件评估属性?或者,有没有人可以替代这里介绍的方法?

【问题讨论】:

  • 感谢您的回答,但看起来logging 模块在字符串格式化方面已经解决了这个问题。当 print 语句的主体不只是 %string.format 时,我正在寻找一个更一般的情况。例如,", ".join([x.foo for x in exes if x in whys])
  • +1 表示“短路”的想法!是的,标准字符串格式已解决,但有时您需要运行昂贵的自定义代码以生成调试输出。

标签: python debugging logging


【解决方案1】:

我想知道在没有处理程序时调用 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 documentation

的稍微修改的示例
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)

在这种情况下,如果关闭优化,则永远不会评估消息字符串。

【讨论】:

    【解决方案2】:

    您可以使用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)')
    

    【讨论】:

      【解决方案3】:

      据我所知,任何具有与 assert -> 相同条件行为的标准 python 语句或函数。

      请注意,如果阈值太高,logging 函数不会执行字符串插值(但您仍然需要为方法调用和内部的一些检查付费)。

      您可以在代码开始时通过monkeypatching logging.logger 扩展Dan D. 的建议:

      import logging
      if __debug__:
          for methname in ('debug', 'info', 'warning', 'error', 'exception'):
               logging.logger.setattr(methname, lambda self, *a, **kwa: None)
      

      然后像往常一样使用日志记录。即使在非优化模式下,您甚至可以更改初始测试以允许替换日志记录方法

      【讨论】:

        【解决方案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__: 移出 of 函数,以便仅在加载包含模块时对其进行测试,则语句的开销只是一个函数调用。

        【讨论】:

        • 嗯...这是记录器模块为我所做的一部分。 logging.debug 接受一个 msg 和 args,然后将其传递给 LogRecord.getMessage() 中的 % 运算符,该运算符仅在需要时调用。日志记录模块甚至为我提供了一种覆盖 LogRecords 工厂的方法,因此我可以放入一个使用 string.format 而不是% 的子类。不过,这还不够通用。
        猜你喜欢
        • 1970-01-01
        • 2015-03-22
        • 2021-12-08
        • 2019-06-06
        • 1970-01-01
        • 1970-01-01
        • 2015-02-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多