A decorator that profiles a method call and logs the profiling result
我想创建一个描述方法并记录结果的装饰器。怎么能做到?
如果需要正确的分析而不是计时,可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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(...) ... |
如果希望对文件名进行更多控制,则需要另一层间接寻址:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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(...) ... |
它看起来很复杂,但是如果您一步一步地遵循它(并注意调用探查器的区别),它应该会变得清晰。
装饰师看起来像:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 |
不管怎样,如果你想做一些严肃的分析,我建议你使用概要文件或cprofile包。
如果您已经了解了如何为cprofile编写装饰器,请考虑使用functools.wrapps。
只需添加一行就可以帮助您更容易地调试装饰器。如果不使用functools.wrapps,修饰函数的名称将为"wrapper",并且的docstring将丢失。
所以改进后的版本是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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(...) ... |
这里是一个具有两个参数的修饰器,配置文件输出的文件名和按结果排序的字段。默认值是累积时间,这对于发现瓶颈很有用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | 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() |
我喜欢@detly的回答。但有时使用snakeviz查看结果是一个问题。
我做了一个稍微不同的版本,将结果作为文本写入同一个文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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(...) ... |
我希望这能帮助别人…