我想创建一个装饰器来分析方法并记录结果。这怎么办?
如果您想要正确的分析而不是计时,您可以使用
cProfile
的未记录功能(来自这个问题):
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(...)
...
它看起来很复杂,但是如果您逐步遵循它(并注意调用分析器的差异),它应该会变得清晰。
装饰器看起来像这样:
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 包。
我喜欢@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(...)
...
我希望这对某人有帮助...
如果您已经了解如何为 cProfile 编写装饰器,请考虑使用 functools.wraps。
只需添加一行即可帮助您更轻松地调试装饰器。如果不使用 functools.wraps,修饰函数的名称将是“wrapper”,并且 的文档字符串将丢失。
所以改进后的版本是
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(...)
...
这是一个带有两个参数的装饰器,配置文件输出的文件名和按结果排序的字段。默认值是累计时间,这对于查找瓶颈很有用。
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()
根据官方文档https://docs.python.org/3/library/profile.html你可以使用像这样的配置文件上下文管理器:
def profiler(func):
def wrapper(*args, **kwargs):
with cProfile.Profile() as pr:
result = func(*args, **kwargs)
pr.print_stats()
return result
return wrapper
@profiler
def your_function():
# some actions