4

有时我会在函数周围散布很多打印件来打印调试输出。为了切换这个调试输出,我想出了这个:

def f(debug=False): 
    print = __builtins__.print if debug else lambda *p: None

或者,如果我需要打印除调试消息之外的其他内容,我会dprint为调试消息创建函数。

问题是,当debug=False这个print语句显着减慢代码时,因为lambda *p: None仍然被调用,并且已知函数调用很慢。

所以,我的问题是:有没有更好的方法来有效地禁用所有这些调试打印,以免影响代码性能?


所有的答案都是关于我不使用logging模块的。这是一个很好的注意事项,但这并没有回答如何避免函数调用大大减慢代码的问题——在我的例子中是 25 次(如果可能的话(例如通过修改函数代码对象来消除所有带有print语句或其他方式的行))。这些答案建议用 替换printlogging.debug这应该更慢。这个问题是关于完全摆脱那些函数调用。

我尝试使用logging而不是lambda *p: None,毫不奇怪,代码变得更慢。


也许有人想看看那些打印导致 25 减速的代码:http: //ideone.com/n5PGu

logging而且我对模块没有任何反对意见。我认为始终坚持稳健的解决方案而没有一些技巧是一个好习惯。但我认为,如果我在 20 行一次性代码片段中使用这些hack ,就没有什么犯罪行为。


print不是作为限制,而是作为建议,也许可以从函数源代码中删除一些行(例如以 开头)并重新编译它?我在下面的答案中列出了这种方法。尽管我希望看到对该解决方案的一些评论,但我欢迎其他解决此问题的方法。

4

5 回答 5

5

您应该改用该logging模块。请参阅http://docs.python.org/library/logging.html

然后,您可以根据需要设置日志级别,并创建多个记录器对象,记录不同主题的日志。

import logging
#set your log level
logging.basicConfig(level=logging.DEBUG)
logging.debug('This is a log message')

在您的情况下:您可以简单地用日志语句替换您的打印语句,例如:

import logging
print = __builtins__.print if debug else logging.debug

现在,如果您将日志记录级别设置为调试,该函数只会打印任何内容

logging.basicConfig(level=logging.DEBUG)

但作为一个优点,您可以在上面使用所有其他日志记录功能!logging.error('error!')

于 2012-08-31T22:34:51.010 回答
3

Ned Batchelder在评论中写道:

我怀疑减速是在计算调试函数的参数时。您应该寻找避免这些计算的方法。预处理 Python 只是一种干扰。

他是对的,因为减速实际上是由使用format方法格式化字符串引起的,无论结果字符串是否被记录,都会发生这种情况。

因此,如果不会发生日志记录,则应推迟并取消字符串格式化。这可以通过重构dprint函数或通过log.debug以下方式使用来实现:

log.debug('formatted message: %s', interpolated_value)

如果消息不会被记录,它不会被格式化,不像print,它总是被格式化,不管它是否会被记录或丢弃。

log.debug推迟格式化的解决方案给Martijn Pieters

于 2012-09-01T17:10:59.083 回答
1

另一种解决方案可能是动态编辑代码f并删除所有drpint调用。但是这个解决方案是非常不推荐使用的

你是对的,你永远不应该诉诸于此,有很多方法可能出错。首先,Python 不是为源代码级别的转换而设计的语言,很难在不破坏有效代码的情况下将其编写为诸如 comment_1 之类的转换器。其次,这种 hack 会在各种情况下中断——例如,在定义方法时、定义嵌套函数时、在 Cython 中使用时、inspect.getsource 因任何原因而失败时。Python 是动态的,你真的不需要这种 hack 来定制它的行为。

以下是这种方法的代码,适合那些喜欢熟悉它的人:

from __future__ import print_function

DEBUG = False

def dprint(*args,**kwargs):
    '''Debug print'''
    print(*args,**kwargs)

_blocked = False
def nodebug(name='dprint'):
    '''Decorator to remove all functions with name 'name' being a separate expressions'''
    def helper(f):      
        global _blocked
        if _blocked:
            return f

        import inspect, ast, sys

        source = inspect.getsource(f)        
        a = ast.parse(source) #get ast tree of f

        class Transformer(ast.NodeTransformer):
            '''Will delete all expressions containing 'name' functions at the top level'''
            def visit_Expr(self, node): #visit all expressions
                try:
                    if node.value.func.id == name: #if expression consists of function with name a
                        return None #delete it
                except(ValueError):
                    pass
                return node #return node unchanged
        transformer = Transformer()
        a_new = transformer.visit(a)
        f_new_compiled = compile(a_new,'<string>','exec')

        env = sys.modules[f.__module__].__dict__
        _blocked = True
        try:
            exec(f_new_compiled,env)
        finally:
            _blocked = False
        return env[f.__name__]         
    return helper


@nodebug('dprint')        
def f():
    dprint('f() started')
    print('Important output')
    dprint('f() ended')
    print('Important output2')


f()

更多信息:动态替换部分功能代码

于 2012-09-01T12:30:10.467 回答
0

作为一个黑客,是的,这是有效的。(而且那些 lambda no-ops 绝对不可能成为您应用程序的瓶颈。)

logging但是,您确实应该使用该模块正确进行日志记录。

有关如何完成此操作的基本示例,请参见http://docs.python.org/howto/logging.html#logging-basic-tutorial 。

于 2012-08-31T22:33:28.580 回答
0

您肯定需要使用 Python 的日志记录模块,它非常实用,您可以更改应用程序的日志级别。例子:

>>> import logging
>>> logging.basicConfig(level=logging.DEBUG)
>>> logging.debug('Test.')
DEBUG:root:Test.
于 2012-08-31T22:35:23.833 回答