【问题标题】:Pandas: How to store cProfile output in a pandas DataFrame?Pandas:如何将 cProfile 输出存储在 pandas DataFrame 中?
【发布时间】:2026-01-18 23:55:02
【问题描述】:

已经存在一些讨论使用 cProfile 进行 python 分析的帖子,以及由于以下示例代码中的输出文件 restats 不是纯文本文件而分析输出的挑战.下面的 sn-p 只是来自docs.python.org/2/library/profile 的一个样本,不能直接复现。

import cProfile
import re
cProfile.run('re.compile("foo|bar")', 'restats')

这里有一个讨论:Profile a python script using cProfile into an external file,在 docs.python.org 上有更多关于如何使用 pstats.Stats 分析输出的详细信息(仍然只是一个示例,不可重现) :

import pstats
p = pstats.Stats('restats')
p.strip_dirs().sort_stats(-1).print_stats()

我可能在这里遗漏了一些非常重要的细节,但我真的很想将输出存储在 pandas DataFrame 中并从那里做进一步的分析。

我认为这会很简单,因为在 iPython 中运行 cProfile.run() 的输出看起来相当整洁:

In[]:
cProfile.run('re.compile("foo|bar")'

Out[]:

关于如何以相同格式将其放入 pandas DataFrame 的任何建议?

【问题讨论】:

    标签: python pandas profiling


    【解决方案1】:

    我知道这已经有了答案,但对于不想费心下载另一个模块的人来说,这里有一个粗略且准备好的脚本,应该可以接近:

    %%capture profile_results    ## uses %%capture magic to send stdout to variable
    cProfile.run("your_function( **run_parms )")
    

    首先运行上述代码,用 stout 的内容填充profile_results,其中包含cProfile 的通常打印输出。

    ## Parse the stdout text and split it into a table
    data=[]
    started=False
    
    for l in profile_results.stdout.split("\n"):
        if not started:
            if l=="   ncalls  tottime  percall  cumtime  percall filename:lineno(function)":
                started=True
                data.append(l)
        else:
            data.append(l)
    content=[]
    for l in data:
        fs = l.find(" ",8)
        content.append(tuple([l[0:fs] , l[fs:fs+9], l[fs+9:fs+18], l[fs+18:fs+27], l[fs+27:fs+36], l[fs+36:]]))
    prof_df = pd.DataFrame(content[1:], columns=content[0])
    

    它不会因优雅或令人愉悦的风格而赢得任何奖项,但它确实将结果表强制转换为可过滤的数据框格式。

    prof_df 
    

    【讨论】:

    • 快速而肮脏的答案。使用内置的 enumerate() 方法消除了对 started 布尔值的需要。只需将 profile_results.stdout.split() 传递给 enumerate() 即可返回索引的可​​迭代对象。
    • 是的,这就是我所需要的,我只想快速补充一句:prof_df.columns=prof_df.columns.str.strip() 需要保留原始列名。
    【解决方案2】:

    看起来https://github.com/ssanderson/pstats-view 可能会做你想做的事(尽管与可视化数据和使其交互相关的不必要的依赖项):

    >>> from pstatsviewer import StatsViewer
    >>> sv = StatsViewer("/path/to/profile.stats")
    >>> sv.timings.columns
    Index(['lineno', 'ccalls', 'ncalls', 'tottime', 'cumtime'], dtype='object')
    

    【讨论】:

    • 安装命令:pip install git+https://github.com/ssanderson/pstats-view.git@master
    【解决方案3】:

    如果您在 cmd 中使用 python -m cProfile your_script.py

    您可以将输出推送到 csv 文件,然后使用 pandas 进行解析 python -m cProfile your_script.py >> output.txt

    然后用 pandas 解析输出

    df = pd.read_csv('output.txt', skiprows=5, sep='    ', names=['ncalls','tottime','percall','cumti    me','percall','filename:lineno(function)'])
    df[['percall.1', 'filename']] = df['percall.1'].str.split(' ', expand=True, n=1)
    df = df.drop('filename:lineno(function)', axis=1)
    

    【讨论】:

      【解决方案4】:

      你可以使用这个功能来完成这个任务

      def convert_to_df(path, offset=6):
          """
          path: path to file
          offset: line number from where the columns start
          """
          with open(path, "r") as f:
              core_profile = f.readlines()
          core_profile = core_profile[offset:]
          cols = core_profile[0].split()
          n = len(cols[:-1])
          data = [_.split() for _ in core_profile[1:]]
          data = [_ if len(_)==n+1 else _[:n]+[" ".join(_[n+1:])] for _ in data]
          data_ = pd.DataFrame(data, columns=cols)
          return data_
      

      【讨论】:

        【解决方案5】:

        如果人们不想使用 %%capture 或通过 CSV,在拼凑的解决方案下面,在这种情况下,通过 (1) 按累积时间对每个 cProfile 排序和 ( 2) 仅将每个 .prof 的顶部结果 (pstats.Stats(f, stream = p_output).sort_stats("cumulative").print_stats(1)) 添加到数据框(连同 .prof 文件名的一部分,以识别测量来自哪个配置文件)。

        请参阅here 了解一些原始代码(确实使用 CSV 作为中介)。

        import io
        import pstats
        import pandas as pd
        import glob
        
        all_files = glob.glob(profiledir + "/*.prof")
        
        li = []
        
        for f in all_files:
            
            p_output = io.StringIO()
        
            prof_stats = pstats.Stats(f, stream = p_output).sort_stats("cumulative").print_stats(1)
        
            p_output = p_output.getvalue()
            p_output = 'ncalls' + p_output.split('ncalls')[-1]
            result = '\n'.join([','.join(line.rstrip().split(None,5)) for line in p_output.split('\n')])
        
            df = pd.read_csv(io.StringIO(result), sep=",", header=0)
            df['module name'] = f.split(' ')[0].split('\\')[1] # differs depending on your file naming convention
            li.append(df) 
        
        df = pd.concat(li, axis=0, ignore_index=True)
        

        【讨论】:

          最近更新 更多