Подсчет вызовов метода python в рамках другого метода
Я на самом деле пытаюсь сделать это на Java, но я сейчас участвую в python, и это заставило меня задуматься, был ли простой/умный способ сделать это с помощью оберток или что-то в этом роде.
Я хочу знать, сколько раз был вызван конкретный метод внутри другого метода. Например:
def foo(z):
#do something
return result
def bar(x,y):
#complicated algorithm/logic involving foo
return foobar
Итак, для каждого вызова bar с различными параметрами я хотел бы знать, сколько раз было вызвано foo, возможно, с выходом следующим образом:
>>> print bar('xyz',3)
foo was called 15 times
[results here]
>>> print bar('stuv',6)
foo was called 23 times
[other results here]
edit: Я понимаю, что могу просто похлопать счетчик внутри бара и сбросить его, когда вернусь, но было бы здорово, если бы была какая-то магия, которую вы могли бы сделать с обертками, чтобы выполнить одно и то же. Это также означало бы, что я мог бы повторно использовать одни и те же обертки в другом месте, не изменяя код внутри метода.
Ответы
Ответ 1
Похоже на пример учебника для декораторов!
def counted(fn):
def wrapper(*args, **kwargs):
wrapper.called += 1
return fn(*args, **kwargs)
wrapper.called = 0
wrapper.__name__ = fn.__name__
return wrapper
@counted
def foo():
return
>>> foo()
>>> foo.called
1
Вы даже можете использовать другой декоратор для автоматизации записи того, сколько раз функция вызывается внутри другой функции:
def counting(other):
def decorator(fn):
def wrapper(*args, **kwargs):
other.called = 0
try:
return fn(*args, **kwargs)
finally:
print '%s was called %i times' % (other.__name__, other.called)
wrapper.__name__ = fn.__name__
return wrapper
return decorator
@counting(foo)
def bar():
foo()
foo()
>>> bar()
foo was called 2 times
Если foo
или bar
могут в конечном итоге называть себя, вам потребуется более сложное решение, включающее стеки, чтобы справиться с рекурсией. Затем вы направляетесь к полнофункциональному профилировщику...
Возможно, этот обернутый материал декоратора, который, как правило, используется для магии, не является идеальным местом для поиска, если вы все еще "учите себя Python!
Ответ 2
Это определяет декоратор для этого:
def count_calls(fn):
def _counting(*args, **kwargs):
_counting.calls += 1
return fn(*args, **kwargs)
_counting.calls = 0
return _counting
@count_calls
def foo(x):
return x
def bar(y):
foo(y)
foo(y)
bar(1)
print foo.calls
Ответ 3
После вашего ответа - вот путь с декоратором factory...
import inspect
def make_decorators():
# Mutable shared storage...
caller_L = []
callee_L = []
called_count = [0]
def caller_decorator(caller):
caller_L.append(caller)
def counting_caller(*args, **kwargs):
# Returning result here separate from the count report in case
# the result needs to be used...
result = caller(*args, **kwargs)
print callee_L[0].__name__, \
'was called', called_count[0], 'times'
called_count[0] = 0
return result
return counting_caller
def callee_decorator(callee):
callee_L.append(callee)
def counting_callee(*args, **kwargs):
# Next two lines are an alternative to
# sys._getframe(1).f_code.co_name mentioned by Ned...
current_frame = inspect.currentframe()
caller_name = inspect.getouterframes(current_frame)[1][3]
if caller_name == caller_L[0].__name__:
called_count[0] += 1
return callee(*args, **kwargs)
return counting_callee
return caller_decorator, callee_decorator
caller_decorator, callee_decorator = make_decorators()
@callee_decorator
def foo(z):
#do something
return ' foo result'
@caller_decorator
def bar(x,y):
# complicated algorithm/logic simulation...
for i in xrange(x+y):
foo(i)
foobar = 'some result other than the call count that you might use'
return foobar
bar(1,1)
bar(1,2)
bar(2,2)
И вот результат (тестируется с Python 2.5.2):
foo was called 2 times
foo was called 3 times
foo was called 4 times