Skip to content

How to Check If a Function Has Been Called in Python

Learn how to track Python function calls using manual counters, decorators, unittest.mock, or function attributes for debugging, profiling, and testing.

Tracking function execution is essential for debugging logic flows, profiling performance, and writing robust unit tests. While simply adding print() statements works for basic debugging, professional Python development relies on more structured techniques like decorators and mocking frameworks.

This guide explores how to monitor function calls using a manual counter (for simple scripts), a reusable decorator (for production code), and the unittest.mock library (for testing).

Method 1: Using a Manual Global Counter

The simplest way to track execution is by incrementing a global variable inside the function. This is quick to implement but clutters the global namespace and isn't thread-safe.

invocation_count = 0

def greet(name):
    global invocation_count
    invocation_count += 1
    print(f"Hello, {name}!")

greet("Alice")
greet("Bob")

print(f"Function called {invocation_count} times.")

Output:

Hello, Alice!
Hello, Bob!
Function called 2 times.

Method 2: Using a Decorator (Reusable Wrapper)

A cleaner, more modular approach is to use a decorator. This allows you to attach tracking logic to any function without modifying its internal code.

The decorator wraps the original function, increments a counter, and then executes the function.

def track_calls(func):
    """Decorator to count function calls."""
    def wrapper(*args, **kwargs):
        wrapper.call_count += 1
        print(f"-> Calling {func.__name__} (Call #{wrapper.call_count})")
        return func(*args, **kwargs)
    
    wrapper.call_count = 0
    return wrapper

@track_calls
def add(a, b):
    return a + b

@track_calls
def subtract(a, b):
    return a - b

add(5, 3)
add(10, 20)
subtract(5, 2)

print(f"Add called: {add.call_count}")
print(f"Subtract called: {subtract.call_count}")

Output:

-> Calling add (Call #1)
-> Calling add (Call #2)
-> Calling subtract (Call #1)
Add called: 2
Subtract called: 1

Method 3: Using unittest.mock (Best for Testing)

When writing tests, you don't want to modify your actual code just to count calls. The standard library's unittest.mock module provides Mock objects specifically for this purpose. They record arguments, call counts, and return values.

from unittest.mock import Mock

def send_email(address, message):
    print(f"Sending email to {address}...")

mock_sender = Mock(wraps=send_email)

mock_sender("alice@example.com", "Welcome!")
mock_sender("bob@example.com", "Hello!")

print(f"Called? {mock_sender.called}")
print(f"Call count: {mock_sender.call_count}")
print(f"Arguments used: {mock_sender.call_args_list}")

mock_sender.assert_any_call("alice@example.com", "Welcome!")
print("Assertion passed: Alice was emailed.")

Output:

Sending email to alice@example.com...
Sending email to bob@example.com...
Called? True
Call count: 2
Arguments used: [call('alice@example.com', 'Welcome!'), call('bob@example.com', 'Hello!')]
Assertion passed: Alice was emailed.

Method 4: Function Attributes (Self-Tracking)

Similar to the decorator approach but simpler for single functions, you can assign an attribute directly to the function object.

def multiply(a, b):
    if not hasattr(multiply, 'calls'):
        multiply.calls = 0
    multiply.calls += 1
    
    return a * b

multiply(2, 3)
multiply(4, 5)

print(f"Multiply called {multiply.calls} times.")

Output:

Multiply called 2 times.

Conclusion

To check if a function has been called:

  1. Use unittest.mock for testing scenarios. It effectively spies on function behavior without changing the code.
  2. Use a Decorator for production code where you need persistent logging or metrics across multiple functions.
  3. Use Function Attributes for simple, self-contained tracking within a single script.
Navigation