包装函数的__call__方法时,确定cProfile中__call__的起源

时间:2018-08-07 18:30:11

标签: python-2.7 monkeypatching cprofile

我有一个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}

1 个答案:

答案 0 :(得分:1)

以下内容结合了@ alex-martelli到resolve的补丁,修补了特殊的__call__方法,以及@ martijn-pieters到solve来正确重命名功能代码对象的问题

import types

def rename_code_object(func, new_name):
    code = func.__code__
    return types.FunctionType(
        types.CodeType(
            code.co_argcount, 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}