Here我提出了另一个问题的解决方案,我提出了如何删除散布在函数代码上的调试输出函数的所有代价高昂的调用(使用空函数{{1}减速25次})。
解决方案是动态编辑功能代码并使用注释符号lambda *p: None
添加所有函数调用。
#
我现在看到的问题是:
from __future__ import print_function
DEBUG = False
def dprint(*args,**kwargs):
'''Debug print'''
print(*args,**kwargs)
def debug(on=False,string='dprint'):
'''Decorator to comment all the lines of the function code starting with string'''
def helper(f):
if not on:
import inspect
source = inspect.getsource(f)
source = source.replace(string, '#'+string) #Beware! Swithces off the whole line after dprint statement
with open('temp_f.py','w') as file:
file.write(source)
from temp_f import f as f_new
return f_new
else:
return f #return f intact
return helper
def f():
dprint('f() started')
print('Important output')
dprint('f() ended')
f = debug(DEBUG,'dprint')(f) #If decorator @debug(True) is used above f(), inspect.getsource somehow includes @debug(True) inside the code.
f()
纪念所有到最后的路线;但可能有其他语句由#
分隔。这可以通过删除;
中的所有pprint
来电来解决,而不是评论,但可能不是那么简单,因为可能存在嵌套的parantheses。f
,然后从中加载新的temp_f.py
代码。没有写入硬盘驱动器应该有更好的方法来做到这一点。我找到了this recipe,但还没有成功。f
的特殊语法应用装饰器,则@debug
包含带有装饰器的行到功能代码。可以从字符串中手动删除此行,但如果有多个装饰器应用于inspect.getsource
,则可能会导致错误。我通过使用旧式装饰器应用程序f
来解决它。你在这里看到了什么其他问题?
如何解决所有这些问题?
这种方法的优点和缺点是什么?
这里可以改进什么?
有没有更好的方法来实现我尝试使用此代码实现的目标?
我认为在编译为字节码之前预处理函数代码是一种非常有趣且有争议的技术。奇怪的是,没有人对此感兴趣。我认为我提供的代码可能有很多不稳定的地方。
答案 0 :(得分:2)
装饰器可以返回包装器,也可以返回未修改的装饰函数。用它来创建一个更好的调试器:
from functools import wraps
def debug(enabled=False):
if not enabled:
return lambda x: x # Noop, returns decorated function unaltered
def debug_decorator(f):
@wraps(f)
def print_start(*args, **kw):
print('{0}() started'.format(f.__name__))
try:
return f(*args, **kw)
finally:
print('{0}() completed'.format(f.__name__))
return print_start
return debug_decorator
debug
函数是装饰器工厂,调用它时会生成装饰器函数。如果禁用调试,它只返回一个lambda,返回它不变的参数,一个no-op装饰器。启用调试时,它会返回一个调试装饰器,它在装饰函数启动时打印,并在返回时再次打印。
然后将返回的装饰器应用于修饰函数。
用法:
DEBUG = True
@debug(DEBUG)
def my_function_to_be_tested():
print('Hello world!')
重申:当DEBUG
设置为false时,my_function_to_be_tested
保持不变,因此运行时性能根本不会受到影响。
答案 1 :(得分:1)
这是我在StackOverflow上提出的另一个问题的答案后我想出的解决方案。
此解决方案不会发表任何评论,只删除独立的dprint
语句。它使用ast
模块并与Abstract Syntax Tree一起使用,它可以让我们避免解析源代码。这个想法写在评论here。
在必要的环境中,写入temp_f.py
将替换为执行f
。提供了此解决方案here。
此外,最后一个解决方案解决了装饰器递归应用程序的问题。它是通过使用_blocked
全局变量来解决的。
此代码解决了问题中要解决的问题。但仍然是suggested not to be used in real projects:
你是对的,你不应该诉诸于此,有这么多 它可能出错的方式。首先,Python不是为其设计的语言 源级转换,很难把它写成变压器 例如comment_1没有无偿破坏有效代码。第二, 这种黑客会在各种情况下破裂 - 例如, 定义方法时,定义嵌套函数时,在使用时 Cython,当inspect.getsource因任何原因失败时。 Python是 足够动态,你真的不需要这种黑客 定制其行为。
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()