1

ここで、関数コードに散在する出力関数をデバッグするためのコストのかかる呼び出しをすべて削除する方法について私が尋ねた他の質問に対する解決策を思いつきました (空の function を使用すると速度が 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これは、コメントではなく内のすべての呼び出しを削除することで対処できますがf、ネストされた括弧がある可能性があるため、それほど簡単ではないかもしれません。
  • temp_f.pyが作成され、そこから新しいfコードがロードされます。ハードドライブに書き込まずにこれを行うためのより良い方法があるはずです。私はこのレシピを見つけましたが、それを機能させることができませんでした.
  • 特殊な構文 used@debugを使用してデコレータが適用されている場合inspect.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関数はデコレータファクトリであり、呼び出されるとデコレータ関数が生成されます。デバッグが無効になっている場合は、引数を変更せずに返すラムダ、ノーオペレーション デコレータを返すだけです。デバッグが有効になっている場合、装飾された関数が開始されたときに出力し、戻ったときに再度出力するデバッグ デコレータを返します。

返されたデコレーターは、装飾された関数に適用されます。

使用法:

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などのトランスフォーマーを作成することは困難です。次に、このハックはあらゆる種類の状況で機能します。たとえば、メソッドを定義するとき、ネストされた関数を定義するとき、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()

その他の関連リンク:

于 2012-09-02T22:56:00.643 に答える