10

我的 python 代码与许多用于(调试|分析|跟踪等)的函数调用交织在一起,例如:

import logging

logging.root.setLevel(logging.DEBUG)
logging.debug('hello')
j = 0
for i in range(10):
    j += i
    logging.debug('i %d j %d' % (i,j))
print(j)
logging.debug('bye')

我想从代码中#define 这些资源消耗函数。类似 c 的东西

#define logging.debug(val)

是的,我知道日志记录模块日志记录级别机制可用于屏蔽低于设置日志级别的日志记录。但是,我要求一种让python解释器跳过函数的通用方法(即使它们没有做太多也需要时间来运行)

一个想法是将我想要注释掉的函数重新定义为空函数:

def lazy(*args): pass
logging.debug = lazy

上面的思路还是调用了一个函数,可能会产生无数其他的问题

4

9 回答 9

17

Python 没有预处理器,尽管您可以通过外部预处理器运行您的python 源代码以获得相同的效果——例如sed "/logging.debug/d",将删除所有调试日志记录命令。虽然这不是很优雅 - 您最终需要某种构建系统来通过预处理器运行所有模块,并且可能在运行主脚本之前创建已处理的 .py 文件的新目录树。

或者,如果您将所有调试语句放在一个if __debug__:块中,当使用 -O(优化)标志运行 python 时,它们将得到优化。

顺便说一句,我用 dis 模块检查了代码,以确保它确实得到了优化。我发现两者

if __debug__: doStuff()

if 0: doStuff()

已优化,但

if False: doStuff()

不是。这是因为 False 是一个普通的 Python 对象,实际上你可以这样做:

>>> False = True
>>> if False: print "Illogical, captain"
Illogical, captain

在我看来,这似乎是语言的一个缺陷——希望它在 Python 3 中得到修复。

编辑:

这在 Python 3 中已修复:分配给 True 或 False现在会给出 SyntaxError。由于 True 和 False 在 Python 3 中是常量,这意味着if False: doStuff()现在已经优化:

>>> def f():
...     if False: print( "illogical")
... 
>>> dis.dis(f)
  2           0 LOAD_CONST               0 (None) 
              3 RETURN_VALUE         
于 2010-01-05T14:33:39.680 回答
2

尽管我认为这个问题是非常清楚和有效的(尽管有很多回应表明并非如此),但简短的回答是“Python 对此不提供支持”。

除了预处理器建议之外,唯一可能的解决方案是使用一些字节码黑客。我什至不会开始想象这在高级 API 方面应该如何工作,但在低级别上,您可以想象检查代码对象的特定指令序列并重写它们以消除它们。

例如,看下面两个函数:

>>> def func():
...    if debug:  # analogous to if __debug__:
...       foo
>>> dis.dis(func)
  2           0 LOAD_GLOBAL              0 (debug)
              3 JUMP_IF_FALSE            8 (to 14)
              6 POP_TOP

  3           7 LOAD_GLOBAL              1 (foo)
             10 POP_TOP
             11 JUMP_FORWARD             1 (to 15)
        >>   14 POP_TOP
        >>   15 LOAD_CONST               0 (None)
             18 RETURN_VALUE

在这里,您可以扫描LOAD_GLOBALof debug,并消除它以及直到JUMP_IF_FALSE目标的所有内容。

这是更传统的 C 风格 debug() 函数,它被预处理器很好地消除了:

>>> def func2():
...    debug('bar', baz)
>>> dis.dis(func2)
  2           0 LOAD_GLOBAL              0 (debug)
              3 LOAD_CONST               1 ('bar')
              6 LOAD_GLOBAL              1 (baz)
              9 CALL_FUNCTION            2
             12 POP_TOP
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE

在这里,您将查找LOAD_GLOBALofdebug并将所有内容擦除到相应的CALL_FUNCTION.

当然,除了最简单的使用模式之外,这两种描述都比你真正需要的要简单得多,但我认为这是可行的。如果没有人做过,会做一个可爱的项目。

于 2010-01-05T15:46:50.910 回答
1

好吧,您总是可以实现自己的简单预处理器来解决问题。或者,更好的是,您可以使用现有的。说http://code.google.com/p/preprocess/

于 2010-01-05T15:13:02.987 回答
0

使用模块范围的变量?

from config_module import debug_flag

并使用此“变量”来控制对日志记录功能的访问。您将自己构建一个logging使用debug_flag来控制日志记录功能的模块。

于 2010-01-05T13:14:12.407 回答
0

我认为完全避免对函数的调用是不可能的,因为 Python 的工作方式与 C 不同。#define 在编译代码之前发生在预编译器中。在 Python 中,没有这样的东西。

如果您想在工作环境中完全删除调试调用,我认为唯一的方法是在执行之前实际更改代码。使用执行之前的脚本,您可以注释/取消注释调试行。

像这样的东西:

文件记录.py

#Main module
def log():
    print 'logging'

def main():
    log()
    print 'Hello'
    log()

文件 call_log.py

import re
#To log or not to log, that's the question
log = True

#Change the loging
with open('logging.py') as f:
    new_data = []
    for line in f:
       if not log and re.match(r'\s*log.*', line):
         #Comment
         line = '#' + line
       if log and re.match(r'#\s*log.*', line):
         #Uncomment
         line = line[1:]
       new_data.append(line)

#Save file with adequate log level
with open('logging.py', 'w') as f:
   f.write(''.join(new_data))


#Call the module
import logging
logging.main()

当然,它有它的问题,特别是如果有很多模块并且很复杂,但是如果你需要绝对避免调用一个函数,它是可以使用的。

于 2010-01-05T14:16:22.613 回答
0

在您执行此操作之前,您是否进行了分析以验证日志记录实际上需要大量时间?您可能会发现尝试删除呼叫所花费的时间比您节省的时间要多。

接下来,您是否尝试过Psyco 之类的东西?如果您已设置好日志以禁用日志记录,那么 Psyco 可能能够优化调用日志记录函数的大部分开销,并注意到它总是会在没有任何操作的情况下返回。

如果您仍然发现日志记录需要相当长的时间,那么您可能希望查看在关键循环中覆盖日志记录函数,可能通过将局部变量绑定到日志记录函数或适当的虚拟函数(或通过检查 None在调用它之前)。

于 2010-01-05T14:57:28.680 回答
0

定义一个什么都不做的函数,即

def nuzzing(*args, **kwargs): pass

然后用你的函数重载你想摆脱的所有函数,ala

logging.debug = nuzzing
于 2010-01-05T23:38:09.417 回答
0

我喜欢 'if __debug_' 解决方案,只是把它放在每次调用之前有点分散注意力和丑陋。我遇到了同样的问题,并通过编写一个脚本来克服它,该脚本自动解析您的源文件并用 pass 语句替换日志语句(并注释掉日志语句的副本)。它还可以撤消此转换。

当我将新代码部署到生产环境中时,我会使用它,因为在生产环境中有很多我不需要的日志语句并且它们正在影响性能。

你可以在这里找到脚本:http: //dound.com/2010/02/python-logging-performance/

于 2010-02-07T21:32:12.743 回答
-1

您不能跳过函数调用。不过,您可以将它们重新定义为空,例如通过创建另一个提供相同接口但具有空函数的日志记录对象。

但到目前为止,最干净的方法是忽略低优先级日志消息(如您​​所建议的):

logging.root.setLevel(logging.CRITICAL)
于 2010-01-05T14:53:59.850 回答