【问题标题】:Profiling a system with extensively reused decorators使用广泛重用的装饰器分析系统
【发布时间】:2012-03-12 07:10:51
【问题描述】:

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

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

有什么办法可以纠正这种情况吗?移除装饰器不是一种选择;它提供了必要的功能。

我们考虑过事后从cProfile数据中手动剥离decorator,但似乎不太可能,因为数据被汇总成caller->callee关系,破坏了caller->decorator->callee关系.

【问题讨论】:

  • 为什么没用?如果分析信息指向装饰器,那么对装饰器实现的任何改进都可能带来巨大的变化。
  • jcollado:因为装饰器是运行时的一个微不足道的部分,但它的被调用者不是。它掩盖了这些被调用者的“真正调用者”是什么,这可能是决定如何优化的关键信息。

标签: python decorator cprofile


【解决方案1】:

使用类似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

【讨论】:

    【解决方案2】:

    我猜不是装饰器本身弄乱了您的分析,而是装饰器创建的 包装函数。发生这种情况是因为所有包装函数都具有相同的名称。要解决这个问题,只需让装饰器更改包装函数的名称即可。

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

    【讨论】:

    • 假设装饰器是用functools.wraps() 之类的东西正确创建的,__name__ 无论如何都已经设置好了。
    • 修饰了这个答案,因为如果分析器从堆栈帧中获取函数名称,它实际上将是代码对象的名称,而不是函数的名称。
    • 我也更新了我的(当它们应该是 posargs 时,我偶然将 cellvars 和 freevars 作为 kwargs 传递了,这就是造成段错误的原因)。我将functools.wraps() 保留在那里,因为这样__module____doc__ 等也可以通过。
    • 鉴于所有这些信息,您将如何在 python 标准库中解决这个问题?我可以想到三种可能的方法:1)在 functools.wraps 中添加“recreate_code”选项 2)让 cProfile 检查 __name__ 而不是 co_name 3)将 func.__name__ 更改为将 co_name 设置为匹配的属性。
    • 我认为最大的问题是 Python 堆栈帧不包含对函数的引用,只包含对代码对象的引用,并且只要有闭包,代码对象就会在函数之间共享。我会在堆栈帧中添加对当前函数的引用,并让 cProfile 使用它。
    猜你喜欢
    • 1970-01-01
    • 2018-11-06
    • 1970-01-01
    • 1970-01-01
    • 2016-06-07
    • 1970-01-01
    • 2018-01-07
    • 2018-06-08
    • 1970-01-01
    相关资源
    最近更新 更多