我们的代码库有一些被广泛使用的装饰器。
当我创建运行时配置文件时,调用图的很大一部分看起来像一个沙漏;许多函数调用一个函数(装饰器),然后该函数调用许多函数。这是一个比我想要的不太有用的配置文件。
有没有办法纠正这种情况?移除装饰器不是一种选择;它提供了必要的功能。
我们考虑过事后从cProfile数据中手动剥离decorator,但这似乎不太可能,因为数据被汇总成caller->callee关系,破坏了caller->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
这里仍然使用以允许传递文档字符串、模块名称等内容)
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
,因此它们应该与分析器中的包装函数分开计算。您可以轻松地将它们设置为原始函数的名称,以便它们的运行时间将与原始函数的名称一起滚动。