Count how many times each function gets called

Question:

I want to count how many times each function get called.
I have a wrapper to do the counting and save it into a global variable

def counter(f):
    global function_calls
    function_calls = 0

    def wrapper(*args, **kwargs):
        global function_calls
        function_calls += 1
        return f(*args, **kwargs)

    return wrapper

and then other two functions to be decorated for counting

@counter
def square(x):
    return x * x


@counter
def addition_by_self(x):
    return x + x

Now when I call the function five time each the global variable function_calls returns 10. Which makes sense.

print(square(x=4))
print(square(x=4))
print(square(x=4))
print(square(x=4))
print(square(x=4))

print(addition_by_self(x=4))
print(addition_by_self(x=4))
print(addition_by_self(x=4))
print(addition_by_self(x=4))
print(addition_by_self(x=4))

print(f"Number of the function got called: {function_calls}")

running the file gives the output.

16
16
16
16
16
8
8
8
8
8
Number of the function got called: 10

Now I need some solutions or ideas on how to make the decorator return how many times each function got called, not an aggregation of all the calls. I might have other functions which I need track the number of times they also got called.

Essentially I want to do something like print(function_calls) # or something proper
and get the out like: sqaure got called 5 times and addition_by_self got called 5 times

Asked By: se7en

||

Answers:

Instead of a single global int, store per-function counts in a dict.

def counter(f):
    global function_calls
    function_calls = {}

    def wrapper(*args, **kwargs):
        global function_calls
        function_calls[f.__name__] = function_calls.setdefault(f.__name__, 0) + 1
        return f(*args, **kwargs)

    return wrapper

f might make a better key than f.__name__ (multiple distinct functions could have the same name), but this works as a simple example.

Answered By: chepner

Using a list and an attribute:

def counter(f):
    global funs
    try:
        len(funs)
    except NameError:
        funs = []
    funs.append(f)
    f.function_calls = 0

    def wrapper(*args, **kwargs):
        f.function_calls += 1
        return f(*args, **kwargs)

    return wrapper

@counter
def square(x):
    return x * x


@counter
def addition_by_self(x):
    return x + x

for i in range(10):
    print(square(3))
    print(addition_by_self(2))
    print(addition_by_self(4))

for f in funs:
    print(f'function: {f.__name__}, calls: {f.function_calls}')
Answered By: Dennis Williamson