【问题标题】:Determine origin of __call__ in cProfile when wrapping a function's __call__ method包装函数的 __call__ 方法时确定 cProfile 中 __call__ 的来源
【发布时间】:2019-01-14 22:16:37
【问题描述】:

我有一个 C++ 库,我正在通过 python 包装和公开它。由于各种原因,我需要在通过 python 公开它们时重载函数的__call__

下面是一个使用time.sleep 模拟具有不同计算时间的函数的最小示例

import sys
import time

class Wrap_Func(object):
    def __init__(self, func, name):
        self.name = name
        self.func = func

    def __call__(self, *args, **kwargs):
        # do stuff
        return self.func(*args, **kwargs)

def wrap_funcs():
    thismodule = sys.modules[__name__]

    for i in range(3):
        fname = 'example{}'.format(i)
        setattr(thismodule, fname, Wrap_Func(lambda: time.sleep(i), fname))

wrap_funcs()

通过cProfile 分析我的代码时,我得到了__call__ 例程的列表。 我无法确定哪些例程占用了大部分计算时间。

>>> import cProfile
>>> cProfile.runctx('example0(); example1(); example2()', globals(), locals())
         11 function calls in 6.000 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        3    0.000    0.000    6.000    2.000 <ipython-input-48-e8126c5f6ea3>:11(__call__)
        3    0.000    0.000    6.000    2.000 <ipython-input-48-e8126c5f6ea3>:20(<lambda>)
        1    0.000    0.000    6.000    6.000 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        3    6.000    2.000    6.000    2.000 {time.sleep}

预期

通过手动定义函数(这需要是动态的和上面的包装器)为

def example0():
    time.sleep(0)

def example1():
    time.sleep(1)

def example2():
    time.sleep(2)

我得到了预期的输出

>>> import cProfile
>>> cProfile.runctx('example0(); example1(); example2()', globals(), locals())
     11 function calls in 6.000 seconds
             8 function calls in 3.000 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 <ipython-input-58-688d247cb941>:1(example0)
        1    0.000    0.000    0.999    0.999 <ipython-input-58-688d247cb941>:4(example1)
        1    0.000    0.000    2.000    2.000 <ipython-input-58-688d247cb941>:7(example2)
        1    0.000    0.000    3.000    3.000 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        3    3.000    1.000    3.000    1.000 {time.sleep}

【问题讨论】:

标签: python-2.7 monkeypatching cprofile


【解决方案1】:

以下结合了@alex-martelli 到resolve 的答案,修补了特殊的__call__ 方法和@martijn-pieters 到solve 正确重命名函数代码对象的问题

编辑:由于PEP 570 有一个新的第二个参数到types.CodeTypecode.co_kwonlyargcount。为早期版本的 Python 删除此参数。

import types

def rename_code_object(func, new_name):
    code = func.__code__
    return types.FunctionType(
        types.CodeType(
            code.co_argcount, 
            code.co_kwonlyargcount,  # comment out this line for earlier versions of python
            code.co_nlocals,
            code.co_stacksize, code.co_flags,
            code.co_code, code.co_consts,
            code.co_names, code.co_varnames,
            code.co_filename, new_name,
            code.co_firstlineno, code.co_lnotab,
            code.co_freevars, code.co_cellvars),
        func.__globals__, new_name, func.__defaults__, func.__closure__)

class ProperlyWrapFunc(Wrap_Func):

    def __init__(self, func, name):
        super(ProperlyWrapFunc, self).__init__(func, name)
        renamed = rename_code_object(super(ProperlyWrapFunc, self).__call__, name)
        self.__class__ = type(name, (ProperlyWrapFunc,), {'__call__': renamed})

在调用使用新类的修改wrap_funcs() 后,我们得到预期的输出。这也应该与异常回溯兼容

>>> import cProfile
>>> cProfile.runctx('example0(); example1(); example2()', globals(), locals())
         11 function calls in 6.000 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    2.000    2.000 <ipython-input-1-96920f80be1c>:9(example0)
        1    0.000    0.000    2.000    2.000 <ipython-input-1-96920f80be1c>:9(example1)
        1    0.000    0.000    2.000    2.000 <ipython-input-1-96920f80be1c>:9(example2)
        3    0.000    0.000    6.000    2.000 <ipython-input-9-ed938f395cb4>:30(<lambda>)
        1    0.000    0.000    6.000    6.000 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        3    6.000    2.000    6.000    2.000 {time.sleep}

【讨论】:

  • 顺便说一句:调用type(name, (ProperlyWrapFunc,), {'__call__': renamed}) 需要扩展以包含基类中的任何其他方法。此外,任何特殊方法或property 类型都需要单独声明
猜你喜欢
  • 2017-10-25
  • 2015-01-03
  • 2012-09-02
  • 2013-10-30
  • 2016-09-23
  • 1970-01-01
  • 2013-07-22
  • 1970-01-01
  • 2023-04-03
相关资源
最近更新 更多