Skip to main content

How to Check If a Function Has Been Called in Python

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}!")

# Usage
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)

# Initialize the counter on the wrapper function itself
wrapper.call_count = 0
return wrapper

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

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

# Usage
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
note

By attaching call_count to the wrapper function object, we avoid global variables entirely.

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

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

# Wrap the real function with a Mock
mock_sender = Mock(wraps=send_email)

# Usage in your application logic
mock_sender("alice@example.com", "Welcome!")
mock_sender("bob@example.com", "Hello!")

# Assertions (Checking if it was called)
print(f"Called? {mock_sender.called}")
print(f"Call count: {mock_sender.call_count}")
print(f"Arguments used: {mock_sender.call_args_list}")

# You can even assert specific calls (useful in unit tests)
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):
# Initialize attribute if it doesn't exist
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.