2

So, I have a code snippet to count the number of function calls, and while browsing for potential optimal solutions, I came across a similar question posted here a few years ago:

is there a way to track the number of times a function is called?

One of the solutions listed in the thread above matched mine, but there was a subtle difference. When I posted my solution and asked about the potential pitfalls in my code, my comment was deleted even though mine was a solution to the question. So, I am hoping this one isn't closed as a duplicate, because frankly I don't know where to turn to.

Here was my solution:

def counter(func):
    counter.count=0
    def wrap(*args,**kwargs):
        counter.count += 1
        print "Number of times {} has been called so far 
            {}".format(func.__name__,counter.count)
        func(*args,**kwargs)
    return wrap

@counter
def temp(name):
    print "Calling {}".format(name)

My counter is defined as an attribute to the decorator 'counter', instead of the wrapper function 'wrap'. The code works as currently defined. But, is there a scenario where it may fail? Am I overlooking something here?

martineau
  • 119,623
  • 25
  • 170
  • 301
  • Sure. If you decorate `foo`, call it a few million times, then define a new function afterwards named `bar` and decorate that too, then your previous counts of `foo` will disappear. – Adam Smith Jul 20 '18 at 20:08
  • 1
    There's a better [implementations](https://wiki.python.org/moin/PythonDecoratorLibrary#Counting_function_calls) in the Python Decorator Library in which the decorator is a class which would make instances of it (i.e. uses of it) independent. – martineau Jul 20 '18 at 20:43
  • Martineau's answer seems to be the best one out there. No matter where I define the counter variable (inside or outside of 'wrap'), it doesn't count the total number of calls across multiple functions. The call count resets when the decorator is applied to a new function. – Prashanth Raghavan Aug 13 '18 at 19:36

2 Answers2

3

If you use this decorator on two separate functions, they'll share the same call count. That's not what you want. Also, every time this decorator is applied to a new function, it will reset the shared call count.

Aside from that, you've also forgotten to pass through the return value of func in wrap, and you can't stick an unescaped line break in the middle of a non-triple-quoted string literal like that.

user2357112
  • 260,549
  • 28
  • 431
  • 505
1

With slight modification to your code you can make your counter decorator independent:

def counter(func, counter_dict={}):
    counter_dict[func]=0
    def wrap(*args,**kwargs):
        counter_dict[func] += 1
        print("Number of times {} has been called so far {}".format(func.__name__, counter_dict[func]))
        return func(*args,**kwargs)
    return wrap

@counter
def temp1(name):
    print("Calling temp1 {}".format(name))

@counter
def temp2(name):
    print("Calling temp2 {}".format(name))


temp1('1')
temp1('2')
temp2('3')

Prints:

Number of times temp1 has been called so far 1
Calling temp1 1
Number of times temp1 has been called so far 2
Calling temp1 2
Number of times temp2 has been called so far 1
Calling temp2 3
Andrej Kesely
  • 168,389
  • 15
  • 48
  • 91