デコレータが広く再利用されているシステムをプロファイリングします

StackOverflow https://stackoverflow.com/questions/9386636

  •  28-10-2019
  •  | 
  •  

質問

私たちのコードベースには、広く使用されているいくつかのデコレータがあります。

ランタイムプロファイルを作成すると、コールグラフの大部分が時間ガラスのように見えます。多くの関数は、1つの関数(デコレーター)を呼び出し、多くの関数を呼び出します。これは、私が望むよりも十分ではないプロファイルです。

この状況を修正する方法はありますか?デコレーターを削除することはオプションではありません。必要な機能を提供します。

事実の後にcrofileデータからデコレーターを手動で剥奪することを検討しましたが、データは発信者 - > callee関係に要約されているため、発信者 - > decorator-> callee関係を破壊するため、不可能と思われます。

役に立ちましたか?

解決

のようなものを使用します new ライブラリ(または types Python 2.6+)では、理論的にはコードオブジェクトを動的に作成し、そのコードオブジェクトに基づいて機能して、ラッピングしている関数とともに変化した組み込み名を持つ機能オブジェクトを作成できます。

それはあなたが物事を深く操作することを可能にします <func>.__code__.co_name (通常は読み取り専用です)。


import functools
import types

def metadec(func):

    @functools.wraps(func)
    def wrapper(*args, **kwargs):   
        # do stuff
        return func(*args, **kwargs)

    c = wrapper.func_code
    fname = "%s__%s" % (func.__name__, wrapper.__name__)

    code = types.CodeType(
                c.co_argcount, 
                c.co_nlocals,
                c.co_stacksize,
                c.co_flags,  
                c.co_code,        
                c.co_consts,         
                c.co_names,
                c.co_varnames,
                c.co_filename,
                fname, # change the name
                c.co_firstlineno,
                c.co_lnotab,
                c.co_freevars,
                c.co_cellvars,
            )

    return types.FunctionType(
            code, # Use our updated code object
            wrapper.func_globals,
            fname, # Use the updated name
            wrapper.func_defaults,
            wrapper.func_closure,
        )

(functools.wraps ここでは、DocStrings、Module Nameなどのもののパススルーを可能にするためにまだ使用されています。


In [1]: from metadec import metadec

In [2]: @metadec
   ...: def foobar(x):
   ...:     print(x)
   ...:     
   ...:     

In [3]: foobar.__name__
Out[3]: 'foobar__wrapper'

In [4]: foobar(1)
1

他のヒント

プロファイリングを乱雑にしているのはデコレーター自体ではなく、 ラッパー関数 デコレーターによって作成されました。そして、それはすべてのラッパー関数が同じ名前を持っているために起こっています。これに対処するには、デコレーターにラッパー関数の名前を変更してもらいます。

def decorator(func):

    def wrapper(*args):
        print "enter func", func.__name__
        return func(*args)

    wrapper.__name__ += "_" + func.__name__
    return wrapper

使用することもできます functools.wraps(), 、しかし、ラッパー関数の名前は、ラッピングの関数の名前と一致します。プロファイリングには問題ないと思います。

これで、関数のコードオブジェクトにも名前があります。 Pythonは、オブジェクトをコードするだけで、スタック上の関数への参照を保存せず、プロファイラーがスタックフレームからラッパー関数の名前を取得している場合、この名前を取得します。通常の方法で定義されたラッパーは、各ラッパー関数のコードオブジェクトと関数オブジェクトを明示的に再構築しない限り、コードオブジェクトを共有します(関数オブジェクトが異なりますが)。これはかなり多くの作業であり、非常にcpython固有のものです(バージョン固有の場合もあります)。しかし、ここにあなたがそれを進める方法はあります:

from types import FunctionType, CodeType    

def decorator(func):

    def wrapper(*args):
        print "enter func", func.__name__
        return func(*args)

    name = wrapper.__name__ + "_" + func.__name__

    func_code = wrapper.func_code
    new_code  = CodeType(
            func_code.co_argcount, func_code.co_nlocals, func_code.co_stacksize,
            func_code.co_flags, func_code.co_code, func_code.co_consts,
            func_code.co_names, func_code.co_varnames, func_code.co_filename,
            name, func_code.co_firstlineno, func_code.co_lnotab,
            func_code.co_freevars, func_code.co_cellvars)
    wrapper   = FunctionType(
            new_code, wrapper.func_globals, name, wrapper.func_defaults,
            wrapper.func_closure)

    return wrapper

関数の名前とコードオブジェクトの名前の両方がここに設定されています wrapper_originalfuncname したがって、それらはプロファイラーのラップされた関数とは別にカウントされるべきです。元の関数の名前だけで元の関数の名前で巻き込まれるように、簡単に元の関数の名前に設定できます。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top