【问题标题】:A decorator that profiles a method call and logs the profiling result一个装饰器,用于分析方法调用并记录分析结果
【发布时间】:2011-07-19 12:54:50
【问题描述】:

我想创建一个装饰器来分析方法并记录结果。如何做到这一点?

【问题讨论】:

  • “个人资料”是什么意思?定时?还是定位代码进行优化?如果是后者,try this.

标签: python profiling decorator


【解决方案1】:

我喜欢@detly 的回答。但有时使用 SnakeViz 查看结果会出现问题。

我做了一个稍微不同的版本,将结果作为文本写入同一个文件:

import cProfile, pstats, io

def profileit(func):
    def wrapper(*args, **kwargs):
        datafn = func.__name__ + ".profile" # Name the data file sensibly
        prof = cProfile.Profile()
        retval = prof.runcall(func, *args, **kwargs)
        s = io.StringIO()
        sortby = 'cumulative'
        ps = pstats.Stats(prof, stream=s).sort_stats(sortby)
        ps.print_stats()
        with open(datafn, 'w') as perf_file:
            perf_file.write(s.getvalue())
        return retval

    return wrapper

@profileit
def function_you_want_to_profile(...)
    ...

我希望这对某人有所帮助...

【讨论】:

  • 谢谢!我前段时间有类似的东西但是丢失了代码,这正是我所需要的。
【解决方案2】:

如果您了解如何为 cProfile 编写装饰器,请考虑使用 functools.wraps

只需添加一行就可以帮助您更轻松地调试装饰器。如果不使用 functools.wraps,装饰函数的名称会是 'wrapper',并且 docstring 会丢失。

所以改进的版本应该是

import cProfile
import functools

def profileit(func):
    @functools.wraps(func)  # <-- Changes here.
    def wrapper(*args, **kwargs):
        datafn = func.__name__ + ".profile" # Name the data file sensibly
        prof = cProfile.Profile()
        retval = prof.runcall(func, *args, **kwargs)
        prof.dump_stats(datafn)
        return retval

    return wrapper

@profileit
def function_you_want_to_profile(...)
    ...

【讨论】:

    【解决方案3】:

    这是一个带有两个参数的装饰器,配置文件输出的文件名和按结果排序的字段。默认值是累积时间,这对于查找瓶颈很有用。

    def profileit(prof_fname, sort_field='cumtime'):
        """
        Parameters
        ----------
        prof_fname
            profile output file name
        sort_field
            "calls"     : (((1,-1),              ), "call count"),
            "ncalls"    : (((1,-1),              ), "call count"),
            "cumtime"   : (((3,-1),              ), "cumulative time"),
            "cumulative": (((3,-1),              ), "cumulative time"),
            "file"      : (((4, 1),              ), "file name"),
            "filename"  : (((4, 1),              ), "file name"),
            "line"      : (((5, 1),              ), "line number"),
            "module"    : (((4, 1),              ), "file name"),
            "name"      : (((6, 1),              ), "function name"),
            "nfl"       : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
            "pcalls"    : (((0,-1),              ), "primitive call count"),
            "stdname"   : (((7, 1),              ), "standard name"),
            "time"      : (((2,-1),              ), "internal time"),
            "tottime"   : (((2,-1),              ), "internal time"),
        Returns
        -------
        None
    
        """
        def actual_profileit(func):
            def wrapper(*args, **kwargs):
                prof = cProfile.Profile()
                retval = prof.runcall(func, *args, **kwargs)
                stat_fname = '{}.stat'.format(prof_fname)
                prof.dump_stats(prof_fname)
                print_profiler(prof_fname, stat_fname, sort_field)
                print('dump stat in {}'.format(stat_fname))
                return retval
            return wrapper
        return actual_profileit
    
    
    def print_profiler(profile_input_fname, profile_output_fname, sort_field='cumtime'):
        import pstats
        with open(profile_output_fname, 'w') as f:
            stats = pstats.Stats(profile_input_fname, stream=f)
            stats.sort_stats(sort_field)
            stats.print_stats()
    

    【讨论】:

      【解决方案4】:

      装饰器看起来像:

      import time
      import logging
      
      def profile(func):
          def wrap(*args, **kwargs):
              started_at = time.time()
              result = func(*args, **kwargs)
              logging.info(time.time() - started_at)
              return result
      
          return wrap
      
      @profile
      def foo():
          pass
      

      无论如何,如果你想做一些严肃的分析,我建议你使用 profile 或 cProfile 包。

      【讨论】:

      • 这不是问的吗?他从来没有问过任何关于时间的事情。他确实询问了关于剖析的问题。
      • 大概是导入时间,而不是timeit。
      【解决方案5】:

      如果您想要正确的分析而不是计时,您可以使用cProfile 的未记录功能(来自this question):

      import cProfile
      
      def profileit(func):
          def wrapper(*args, **kwargs):
              datafn = func.__name__ + ".profile" # Name the data file sensibly
              prof = cProfile.Profile()
              retval = prof.runcall(func, *args, **kwargs)
              prof.dump_stats(datafn)
              return retval
      
          return wrapper
      
      @profileit
      def function_you_want_to_profile(...)
          ...
      

      如果您想更好地控制文件名,那么您将需要另一层间接:

      import cProfile
      
      def profileit(name):
          def inner(func):
              def wrapper(*args, **kwargs):
                  prof = cProfile.Profile()
                  retval = prof.runcall(func, *args, **kwargs)
                  # Note use of name from outer scope
                  prof.dump_stats(name)
                  return retval
              return wrapper
          return inner
      
      @profileit("profile_for_func1_001")
      def func1(...)
          ...
      

      它看起来很复杂,但是如果您一步一步地遵循它(并注意调用分析器的区别),它应该会变得清晰。

      【讨论】:

      • some_variation_on 是什么还不是很清楚。我认为它是一个文件,因为 dump_stats 需要一个文件?但它看起来像一种方法。
      • @lacks - 它只是一个假设的函数,它采用函数的名称并将其转换为文件名,例如。 func.__name__ + .profile。它不必是任何东西。
      • 您可以更清楚地说明这一点,因为它在范围之外调用了一个可以是任何东西的方法,并且在您真正知道它在做什么之前甚至不清楚它是否会成为一个文件名!在这里放一个字符串或注释解释会更有帮助!
      • 您如何阅读.profile 文件?它似乎不是文本。
      • 截至 2016 年,可视化配置文件的规范方法是 SnakeVizRunSnakeRun 的 Python 3 兼容继承者。 (你知道...只是说说而已。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-28
      • 1970-01-01
      • 2012-03-12
      • 1970-01-01
      相关资源
      最近更新 更多