13

I'm actually trying doing this in Java, but I'm in the process of teaching myself python and it made me wonder if there was an easy/clever way to do this with wrappers or something.

I want to know how many times a specific method was called inside another method. For example:

def foo(z): #do something return result def bar(x,y): #complicated algorithm/logic involving foo return foobar 

So for each call to bar with various parameters, I'd like to know how many times foo was called, perhaps with output like this:

>>> print bar('xyz',3) foo was called 15 times [results here] >>> print bar('stuv',6) foo was called 23 times [other results here] 

edit: I realize I could just slap a counter inside bar and dump it when I return, but it would be cool if there was some magic you could do with wrappers to accomplish the same thing. It would also mean I could reuse the same wrappers somewhere else without having to modify any code inside the method.

4
  • in Java you can do it with an aspect, use cflow to check the calling method and call to match the method(s) to count, then implement around advice to increment a counter per matched method (maybe use a map of method signatures and counts) Commented Aug 19, 2009 at 18:25
  • or to just count invocations, simply match execution instead of using cflow and call Commented Aug 19, 2009 at 18:26
  • I was going to point you to a Pyhon code coverage tool (coverage.py), because surely it must contain helpful techniques for what you want to do. But then the author himself (Hi Ned!) answered your question. You might want to look at nedbatchelder.com/code/coverage anyway. Commented Aug 19, 2009 at 19:02
  • Hi df! Ironically I didn't mention coverage.py... Commented Aug 19, 2009 at 19:03

3 Answers 3

22

Sounds like almost the textbook example for decorators!

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 

You could even use another decorator to automate the recording of how many times a function is called inside another function:

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 

If foo or bar can end up calling themselves, though, you'd need a more complicated solution involving stacks to cope with the recursion. Then you're heading towards a full-on profiler...

Possibly this wrapped decorator stuff, which tends to be used for magic, isn't the ideal place to be looking if you're still ‘teaching yourself Python’!

Sign up to request clarification or add additional context in comments.

2 Comments

I am getting foo was called 0 times in the second example.
after my brain parses the second example, it returns 0 as well (see comment above from Krzystztof Slowinisky)
7

This defines a decorator to do it:

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 

6 Comments

So, do all decorated functions share this global variable? It looks like they do. >>> print CALLS 2 >>> @count_calls ... def foobar(y): ... return y ... >>> foobar(1) 1 >>> print CALLS 1
I changed the code to keep the count on a function attribute, so now they'll be kept separate.
This works, but it won't count how many times ONLY bar has called foo. You'd have to add some logic to determine the caller of the foo, which I'm guessing is possible, but I don't know offhand how to do it.
Thanks Ned, this is really close. But I think Triptych is right. I was thinking of something more like a wrapper on bar that took foo as a parameter. The bar wrapper would then wrap foo to count calls, and print the count after bar had executed.
I don't have complete code, but this might be helpful: sys._getframe(1).f_code.co_name is the name of your caller.
|
2

After your response - here's a way with a decorator 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) 

And here's the output (tested with Python 2.5.2):

foo was called 2 times foo was called 3 times foo was called 4 times 

2 Comments

I realize I could just add a counter somewhere inside the bar method and just print it out when I return, but I was wondering if this was possible without actually changing any code within bar.
Ah - gotcha - I edited to put in a new answer with a factory to make both decorators that cooperate.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.