7

How to track the number of recursive calls without using global variables in Python. For example, how to modify the following function to keep track the number of calls?

def f(n):
    if n == 1:
        return 1
    else:
        return n * f(n-1)

print f(5)
2
  • 2
    All calls ever? That's global by definition. Or are you interested in the number of calls still in progress at a given point in time? That ought to be doable, though only with some introspection. Commented Jan 25, 2013 at 1:08
  • How is the information (call count) to be retrieved? Commented Jan 25, 2013 at 2:54

5 Answers 5

10

Here's a neat trick that doesn't use a global: you can stash the counter in the function itself.

def f(n):
    f.count += 1
    if n == 1:
        return 1
    else:
        return n * f(n-1)

After which:

>>> f.count = 0 # initialize the counter
>>> f(5)
120
>>> f.count
5
>>> f(30)
265252859812191058636308480000000L
>>> f.count
35

This handles the "all calls ever" case, anyhow.

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

7 Comments

that's a cool trick, but isn't f.count a global too, by definition? Is this better than passing a reference in a argument, somehow?
IMO the initialization of f.count to 0 makes this quite an ugly (and confusing) solution.
@goncalopp well I guess technically it's not global - the variable is an attribute of f, however it still carries all the problems of a global.
@goncalopp: count isn't in globals(), so there's certainly a defensible definition of "global" which makes it not one. @MikeVella: I'm not a fan of hacking function attributes myself, and would never use it in production code, but I find f.count = 0 the least confusing part of this. I certainly find it less ugly than the hasattr checks I would need otherwise, but beauty is in the eye of the beholder, I guess.
f.count is an attribute of the global variable f (which is the function object), so it's not entirely clear that doing it this way satisfies the requirement of not using a global variables.
|
7

As delnan said, if you want all calls ever, it's impossible without a global, so I'm assuming you just want the call depth, for which you just need to add a return value

def f(n):
    if n == 1:
        return 1,0
    else:
        x, call_depth= f(n-1)
        return n * x, call_depth+1

If you're dealing with several recursive calls, you can do a max(call_depth1, call_depth2) (depth of longest call tree) or just sum the two (total number of actual calls)

6 Comments

it's not impossible without a global, see my answer below.
@goncalopp It seems to me that this answer can give me the total number of calls ever, which is what I need, so why do you mention it is impossible?
goncalopp: This gives an answer that is off-by-one because call depth is not the same as number of calls.
@martineau Yes I notice this point. Still, I can get either total number of calls or call depth by using this kind of method.
@snow: When others say "total number of calls" they mean if you explicitly call it more than once, say f(3) then another line with f(4), that you want the total number from each of these two calls added together.
|
6

This method will give you the total number of times the function has run:

def f(n):
    if hasattr(f,"count"):
        f.count += 1
    else:
        f.count = 1
    if n == 1:
        return 1
    else:
        return n * f(n-1)

1 Comment

Problem: Only one call to f() will count correctly. You should probably reset the attribute before returning 1.
4

Here's a way that uses the stack instead of global variables. As shown it tallies the number of calls to the function including the initial one, not just the number of recursive calls the function made to itself. To make it do that, just move the ncalls += 1 to the beginning of the else statements.

def f(n, ncalls=0):
    ncalls += 1
    if n == 1:
        return 1, ncalls
    else:
        res, ncalls = f(n-1, ncalls)
        return n * res, ncalls

for n in xrange(1, 6):
    print 'f({}): {:4d} ({} calls)'.format(n, *f(n))

Output:

f(1):    1 (1 calls)
f(2):    2 (2 calls)
f(3):    6 (3 calls)
f(4):   24 (4 calls)
f(5):  120 (5 calls)

3 Comments

beautiful, wish I'd thought of that!
The number of calls being same as initial value of n is only true for this particular example. So I prefer your original answer better.
snow: OK, I removed the alternative in the update and simplified the original.
0

You could add an argument to keep count of the calls in (would need to be something mutable), where it gets incremented at the start of the function.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.