デコレータが広く再利用されているシステムをプロファイリングします
質問
私たちのコードベースには、広く使用されているいくつかのデコレータがあります。
ランタイムプロファイルを作成すると、コールグラフの大部分が時間ガラスのように見えます。多くの関数は、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
したがって、それらはプロファイラーのラップされた関数とは別にカウントされるべきです。元の関数の名前だけで元の関数の名前で巻き込まれるように、簡単に元の関数の名前に設定できます。