Я хочу создать декоратор, который профилирует метод и регистрирует результат. Как это можно сделать?
Декоратор, который профилирует вызов метода и регистрирует результат профилирования
Ответ 1
Декоратор будет выглядеть примерно так:
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.
Ответ 2
Если вы хотите правильное профилирование вместо времени, вы можете использовать недокументированную функцию cProfile
(from этот вопрос):
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(...)
...
Это выглядит сложным, но если вы будете следовать ему шаг за шагом (и обратите внимание на разницу при вызове профилировщика), он должен стать ясным.
Ответ 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
Если вы поняли, как написать декоратор для cProfile, рассмотрите возможность использования functools.wraps.
Просто добавляет, что одна строка может помочь вам отлаживать декораторы намного проще. Без использования functools.wraps имя украшенной функции было бы "wrapper", а docstring of был бы потерян.
Таким образом, улучшенная версия будет
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(...)
...
Ответ 5
Мне нравится ответ @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(...)
...
Я надеюсь, что это помогает кому-то...