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

发布于 2025-01-08 00:28:43 字数 254 浏览 4 评论 0原文

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

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

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

我们考虑过事后手动从 cProfile 数据中剥离装饰器,但这似乎不可能,因为数据被汇总为调用者->被调用者关系,这破坏了调用者->装饰器->被调用者关系。

Our code base has a few decorators that are used extensively.

When I create a runtime profile, a large part of the call graph looks like an hour glass; many functions call one function (the decorator), which then calls many functions. This is a less-useful profile than I'd like.

Is there any way to rectify this situation? Removing the decorator is not an option; it provides necessary functionality.

We've considered manually stripping the decorator from the cProfile data after the fact, but it doesn't seem possible, because the data is summarized into caller->callee relationships, which destroys the caller->decorator->callee relationship.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

花想c 2025-01-15 00:28:43

使用类似 new 库(或 types in Python 2.6+),理论上你可以动态创建一个代码对象,然后基于该代码对象有一个内置名称,该名称随您使用的函数而变化包装。

这将允许您操作深达 .__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

Using something like the new library (or types in Python 2.6+), you could theoretically dynamically create a code object and then a function object based on that code object that had a built-in name that varied along with the function you were wrapping.

That would allow you to manipulate things as deep as <func>.__code__.co_name (which is normally read-only).


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 is still used here in order to allow for pass-through of things like docstrings, module names, etc.)


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
幸福不弃 2025-01-15 00:28:43

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

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

I'm going to guess that it's not the decorator itself that's cluttering up your profiling, but rather the wrapper function created by the decorator. And that's happening because all the wrapper functions have the same name. To address this, just have the decorator change the name of the wrapper function.

def decorator(func):

    def wrapper(*args):
        print "enter func", func.__name__
        return func(*args)

    wrapper.__name__ += "_" + func.__name__
    return wrapper

You could also use functools.wraps(), but then the name of the wrapper function will match the name of the function it's wrapping. I guess that would be OK for profiling.

Now, the function's code object also has a name. Python doesn't store references to functions on the stack, only to code objects, so if the profiler is getting the name of the wrapper function from a stack frame, it will get this name. Wrappers defined in the usual way share the code object (even though the function object is different) unless you explicitly rebuild the code object and the function object for each wrapper function. This is quite a bit more work and very CPython-specific (might even be version-specific). But here's how you might go about it:

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

Both the function's name and the code object's name are set here to wrapper_originalfuncname and they should thus be counted separately from the wrapped function in the profiler. You could easily set them to just the original function's name so that their run time would be rolled in with the original function's instead.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文