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()

我现在看到的问题是:

  • #commets 所有行到最后;但可能还有其他语句以 . 分隔;。这可以通过删除所有pprint调用来解决f,而不是评论,但它可能不是那么微不足道,因为可能存在嵌套的括号。
  • temp_f.py被创建,然后f从中加载新代码。应该有更好的方法来做到这一点,而无需写入硬盘驱动器。我找到了这个食谱,但没能成功。
  • 如果使用特殊语法应用装饰器,@debuginspect.getsource在函数代码中包含带有装饰器的行。此行可以手动从字符串中删除,但如果有多个装饰器应用于f. 我通过使用旧式装饰器应用程序解决了这个问题f=decorator(f)

您在这里看到了哪些其他问题?

如何解决所有这些问题?

这种方法的优点和缺点是什么?

这里有什么可以改进的?

有没有更好的方法来做我试图用这段代码实现的目标?


我认为在编译为字节码之前预处理函数代码是一种非常有趣且有争议的技术。奇怪的是没有人对此感兴趣。我认为我给出的代码可能有很多不可靠的地方。

4

2 回答 2

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,它返回它的参数不变,一个无操作装饰器。启用调试后,它会返回一个调试装饰器,该装饰器在装饰函数启动时打印,并在返回时再次打印。

然后将返回的装饰器应用于装饰函数。

用法:

DEBUG = True

@debug(DEBUG)
def my_function_to_be_tested():
    print('Hello world!')

重申一下:当DEBUG设置为 false 时,my_function_to_be_tested保持不变,因此运行时性能完全不受影响。

于 2012-09-01T16:40:01.603 回答
1

这是我在对我在 StackOverflow 上提出的另一个问题的回答后提出的解决方案。

此解决方案不评论任何内容,只是删除独立dprint语句。它使用ast模块并与抽象语法树一起使用,它让我们避免解析源代码。这个想法写在这里的评论中。

在必要的环境中,写入temp_f.py被替换为执行。此处f提供了此解决方案。

此外,最后一个解决方案解决了装饰器递归应用程序的问题。它通过使用_blocked全局变量来解决。

此代码解决了问题中要求解决的问题。但是,建议不要在实际项目中使用

你是对的,你永远不应该诉诸于此,有很多方法可能出错。首先,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-02T22:56:00.643 回答