11

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

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

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

事後にcProfileデータからデコレータを手動で削除することを検討しましたが、データが呼び出し元->呼び出し先の関係に要約され、呼び出し元->デコレータ->呼び出し先の関係が破壊されるため、不可能と思われます。

4

2 に答える 2

6

newライブラリ(またはPython 2.6以降)のようなものを使用すると、types理論的には動的にコードオブジェクトを作成し、そのコードオブジェクトに基づいて関数オブジェクトを作成できます。このコードオブジェクトには、ラップする関数に応じて異なる名前が組み込まれています。

<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ここでは、docstring、モジュール名などのパススルーを可能にするために引き続き使用されます)


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
于 2012-02-21T23:41:31.470 に答える
5

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

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、プロファイラーでラップされた関数とは別にカウントする必要があります。それらを元の関数の名前だけに簡単に設定して、実行時間が元の関数の名前にロールインされるようにすることができます。

于 2012-02-21T23:06:05.573 に答える