繁体   English   中英

Python:计算给定行执行的次数

[英]Python: counting how many times a given line is executed

问题

出于教学目的,我想计算一个给定行在给定函数中执行多少次而不修改或装饰它 例如,对于功能:

def binary_search(seq, x):
    (a, b) = (0, len(seq) - 1)
    while a <= b:
        m = (a + b) / 2
        if x < seq[m]:
            b = m - 1
        elif x > seq[m]:
            a = m + 1
        else:
            return m

我会写这样的东西:

print count_exec(binary_search, range(100), 44, line_number = 4) 

......甚至是这样的:

print count_exec(binary_search(range(100), 44), line = "m = (a + b) / 2")

...两者都应该打印执行第4行的次数(即7)。 最终目标是为任何功能的复杂性提供经验方法:

二元搜索的复杂性

非解决方案

我目前的解决方案是添加一个函数属性:

def binary_search(seq, x):
    binary_search.count = 0 # <------------------ added
    (a, b) = (0, len(seq) - 1)
    while a <= b:
        binary_search.count += 1 # <------------- added
        m = (a + b) / 2
        if x < seq[m]:
            b = m - 1
        elif x > seq[m]:
            a = m + 1
        else:
            return m

binary_search(range(100), 44)
print binary_search.count

我想我可以创建一个装饰函数count_this_line

def binary_search(seq, x):
    (a, b) = (0, len(seq) - 1)
    while a <= b:
        count_this_line() # <-------------------- added
        m = (a + b) / 2
        ...

也许可以装饰函数binary_search本身,但对我来说,这就像修改它一样。

思路

  • 标准库ast可以检索任何给定脚本的抽象语法树,甚至可以执行它。
  • 我没有使用Python分析器的经验。 对我的需求来说,这似乎很重要。 这可能是要走的路吗?

您可以使用line_profiler模块执行此操作( 请参阅文档 )。 请注意,我必须从分叉回购中获得3.x兼容版本 - 不确定它是否已合并。

例如。 我把你的二进制搜索功能放在一个文件中,然后添加:

prof = profile(binary_search)
prof(range(100), 44)

这与文档中提到的@profile装饰器相同,但您不必修改原始代码。 我跑了

kernprof.py -l binsearch.py
python -m line_profiler binsearch.py.lprof

突然出现了这个:

Function: binary_search at line 1
Total time: 4.1e-05 s

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     1                                           def binary_search(seq, x):
     2         1            6      6.0     14.6      (a, b) = (0, len(seq) - 1)
     3         7            8      1.1     19.5      while a <= b:
     4         7            7      1.0     17.1          m = (a + b) // 2
     5         7            8      1.1     19.5          if x < seq[m]:
     6         2            2      1.0      4.9              b = m - 1
     7         5            5      1.0     12.2          elif x > seq[m]:
     8         4            4      1.0      9.8              a = m + 1
     9                                                   else:
    10         1            1      1.0      2.4              return m

“Hits”是您正在寻找的数字。 作为奖励,您也可以获得时间信息,但是对于许多执行来说这会更准确。

根据Jason的建议,我编写了一个纯Python解决方案:

import line_profiler
import __builtin__
import cStringIO
import re

def profile(path, function_call, line_number):
    prof = line_profiler.LineProfiler()
    __builtin__.__dict__['profile'] = prof
    script = open(path).read()
    ns = locals()
    function_name = function_call[:function_call.index("(")]
    rex = re.compile("((?ms)^def %s.+)" % function_name)
    script = rex.sub(r"@profile\n\1\n%s" % function_call, script)
    exec(script, ns, ns)
    stream = cStringIO.StringIO()
    prof.print_stats(stream)
    s = stream.getvalue()
    stream.close()
    return int(re.search(r"(?m)^\s*%s\s*(\S*)" % (line_number+1), s).group(1))

if __name__ == "__main__":
    print profile("binary_search.py", "binary_search(range(100), 44)", 3)

它读取包含要分析的函数的脚本的源,装饰这个,将所需的调用追加到结尾,执行它,将统计信息转储到字符串中,提取给定行号的命中数,并将其返回为一个int 它按要求工作,但具有重要的性能损失。

也许更好的解决方案是删除分析器,但保持动态装饰和执行源代码的想法。 如果我植入它,我会编辑我的答案。

无论如何,谢谢Jason为我提供了一条出路!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM