使用广泛重用的装饰器分析系统

时间:2012-02-21 22:55:55

标签: python decorator cprofile

我们的代码库有一些广泛使用的装饰器。

当我创建运行时配置文件时,调用图的很大一部分看起来像一个小时玻璃;许多函数调用一个函数(装饰器),然后调用许多函数。这是一个不太有用的个人资料。

有没有办法纠正这种情况?删除装饰器不是一种选择;它提供了必要的功能。

我们考虑过事后从cProfile数据中手动剥离装饰器,但似乎不可能,因为数据被归纳为调用者 - >被调用者关系,这会破坏调用者 - &gt ; decorator-> callee relationship。

2 个答案:

答案 0 :(得分: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以允许传递文档字符串,模块名称等内容。)


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

答案 1 :(得分: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,因此它们应与分析器中的包装函数分开计算。您可以轻松地将它们设置为原始函数的名称,以便它们的运行时间将与原始函数一起滚动。