How to Check If a Function Has Been Called in Python

PythonPythonBeginner
Practice Now

Introduction

In this lab, you'll learn how to check if a function has been called in Python, a crucial skill for debugging, performance analysis, and testing. The lab explores techniques for tracking function invocations, including the number of times a function is called and the arguments used.

The lab starts with a simple Python function example and introduces a basic technique for manually tracking function calls using a global variable to count invocations. It then guides you through modifying the code to increment the counter each time the function is called and display the total number of invocations. The lab will continue to explore more advanced methods for monitoring function calls, such as creating wrapper functions and using the unittest.mock module.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/BasicConceptsGroup(["Basic Concepts"]) python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python/BasicConceptsGroup -.-> python/variables_data_types("Variables and Data Types") python/FunctionsGroup -.-> python/function_definition("Function Definition") python/FunctionsGroup -.-> python/arguments_return("Arguments and Return Values") python/AdvancedTopicsGroup -.-> python/decorators("Decorators") subgraph Lab Skills python/variables_data_types -.-> lab-559518{{"How to Check If a Function Has Been Called in Python"}} python/function_definition -.-> lab-559518{{"How to Check If a Function Has Been Called in Python"}} python/arguments_return -.-> lab-559518{{"How to Check If a Function Has Been Called in Python"}} python/decorators -.-> lab-559518{{"How to Check If a Function Has Been Called in Python"}} end

Understand Function Invocation Tracking

In this step, we'll explore the fundamental concept of tracking function invocations in Python. Understanding how many times a function is called and with what arguments is crucial for debugging, performance analysis, and testing. We'll start with a simple example and then introduce a basic technique for manually tracking function calls.

Let's begin by creating a simple Python function in the ~/project directory using the VS Code editor. Create a file named my_function.py and add the following code:

## ~/project/my_function.py
def greet(name):
  """This function greets the person passed in as a parameter."""
  print(f"Hello, {name}!")

## Example usage
greet("Alice")
greet("Bob")

This code defines a function greet that takes a name as input and prints a greeting. It then calls the function twice with different names.

To run this script, open your terminal in VS Code (you can usually do this by pressing Ctrl + `` or Cmd + ``) and execute the following command:

python ~/project/my_function.py

You should see the following output:

Hello, Alice!
Hello, Bob!

Now, let's modify the my_function.py file to track how many times the greet function is called. We'll introduce a global variable to keep track of the invocation count.

Modify the my_function.py file to include the following code:

## ~/project/my_function.py
invocation_count = 0

def greet(name):
  """This function greets the person passed in as a parameter."""
  global invocation_count
  invocation_count += 1
  print(f"Hello, {name}!")

## Example usage
greet("Alice")
greet("Bob")

print(f"The greet function was called {invocation_count} times.")

In this modified version, we've added a global variable invocation_count and incremented it each time the greet function is called. We also added a print statement at the end to display the total number of invocations.

Run the script again using the same command:

python ~/project/my_function.py

You should now see the following output:

Hello, Alice!
Hello, Bob!
The greet function was called 2 times.

This simple example demonstrates a basic way to track function invocations. However, this approach can become cumbersome for more complex applications with many functions. In the next steps, we'll explore more sophisticated techniques using wrapper functions and the unittest.mock module.

Create a Wrapper Function

In this step, we'll learn how to use wrapper functions to track function invocations. A wrapper function is a function that wraps around another function, allowing you to add functionality before or after the original function is executed. This is a powerful technique for monitoring function calls without modifying the original function's code.

Let's continue with the greet function from the previous step. We'll create a wrapper function that tracks the number of times greet is called.

Modify the my_function.py file in the ~/project directory to include the following code:

## ~/project/my_function.py
invocation_count = 0

def greet(name):
  """This function greets the person passed in as a parameter."""
  print(f"Hello, {name}!")

def greet_wrapper(func):
  """This is a wrapper function that tracks the number of times the wrapped function is called."""
  def wrapper(*args, **kwargs):
    global invocation_count
    invocation_count += 1
    result = func(*args, **kwargs)
    return result
  return wrapper

## Apply the wrapper to the greet function
greet = greet_wrapper(greet)

## Example usage
greet("Alice")
greet("Bob")

print(f"The greet function was called {invocation_count} times.")

In this code:

  • We define a greet_wrapper function that takes a function (func) as input.
  • Inside greet_wrapper, we define another function called wrapper. This wrapper function is the actual wrapper that will be executed when we call the wrapped function.
  • The wrapper function increments the invocation_count, calls the original function (func) with any arguments passed to it, and returns the result.
  • We then apply the wrapper to the greet function by assigning greet = greet_wrapper(greet). This replaces the original greet function with the wrapped version.

Run the script again using the same command:

python ~/project/my_function.py

You should see the following output:

Hello, Alice!
Hello, Bob!
The greet function was called 2 times.

This output is the same as in the previous step, but now we're using a wrapper function to track the invocations. This approach is more flexible because you can easily apply the same wrapper to multiple functions.

Now, let's add another function and apply the same wrapper to it. Add the following function to the my_function.py file:

## ~/project/my_function.py
invocation_count = 0

def greet(name):
  """This function greets the person passed in as a parameter."""
  print(f"Hello, {name}!")

def add(x, y):
  """This function adds two numbers and returns the result."""
  print(f"Adding {x} and {y}")
  return x + y

def greet_wrapper(func):
  """This is a wrapper function that tracks the number of times the wrapped function is called."""
  def wrapper(*args, **kwargs):
    global invocation_count
    invocation_count += 1
    result = func(*args, **kwargs)
    return result
  return wrapper

## Apply the wrapper to the greet function
greet = greet_wrapper(greet)
add = greet_wrapper(add)

## Example usage
greet("Alice")
greet("Bob")
add(5, 3)

print(f"The greet function was called {invocation_count} times.")

We've added an add function and applied the greet_wrapper to it as well. Now, the invocation_count will track the total number of calls to both greet and add.

Run the script again:

python ~/project/my_function.py

You should see output similar to this:

Hello, Alice!
Hello, Bob!
Adding 5 and 3
The greet function was called 3 times.

As you can see, the invocation_count now reflects the total number of calls to both the greet and add functions. Wrapper functions provide a clean and reusable way to monitor function invocations.

Use unittest.mock to Monitor Calls

In this step, we'll explore how to use the unittest.mock module to monitor function calls. The unittest.mock module is a powerful tool for testing and debugging, and it provides a convenient way to track function invocations, arguments, and return values.

Let's continue with the greet function from the previous steps. We'll use unittest.mock to monitor the calls to greet.

Modify the my_function.py file in the ~/project directory to include the following code:

## ~/project/my_function.py
from unittest import mock

def greet(name):
  """This function greets the person passed in as a parameter."""
  print(f"Hello, {name}!")

## Create a mock object for the greet function
mock_greet = mock.Mock(wraps=greet)

## Example usage
mock_greet("Alice")
mock_greet("Bob")

## Print the number of times the function was called
print(f"The greet function was called {mock_greet.call_count} times.")

## Print the arguments the function was called with
print(f"The calls were: {mock_greet.call_args_list}")

In this code:

  • We import the mock module from unittest.
  • We create a mock.Mock object that wraps the greet function. The wraps argument tells the mock object to call the original greet function when it's called.
  • We then call the mock_greet object instead of the original greet function.
  • Finally, we use the call_count and call_args_list attributes of the mock object to get information about the function calls.

Run the script again using the same command:

python ~/project/my_function.py

You should see output similar to this:

Hello, Alice!
Hello, Bob!
The greet function was called 2 times.
The calls were: [call('Alice'), call('Bob')]

This output shows that the greet function was called twice, and it also shows the arguments that were passed to the function in each call.

Now, let's examine the call_args_list more closely. It's a list of call objects, each representing a single call to the mocked function. You can access the arguments of each call using the args and kwargs attributes of the call object.

For example, let's modify the code to print the arguments of the first call:

## ~/project/my_function.py
from unittest import mock

def greet(name):
  """This function greets the person passed in as a parameter."""
  print(f"Hello, {name}!")

## Create a mock object for the greet function
mock_greet = mock.Mock(wraps=greet)

## Example usage
mock_greet("Alice")
mock_greet("Bob")

## Print the number of times the function was called
print(f"The greet function was called {mock_greet.call_count} times.")

## Print the arguments the function was called with
print(f"The calls were: {mock_greet.call_args_list}")

## Print the arguments of the first call
if mock_greet.call_args_list:
  print(f"The arguments of the first call were: {mock_greet.call_args_list[0].args}")

Run the script again:

python ~/project/my_function.py

You should see output similar to this:

Hello, Alice!
Hello, Bob!
The greet function was called 2 times.
The calls were: [call('Alice'), call('Bob')]
The arguments of the first call were: ('Alice',)

This output shows that the first call to greet was made with the argument "Alice".

The unittest.mock module provides a powerful and flexible way to monitor function calls in Python. It's a valuable tool for testing, debugging, and performance analysis.

Summary

In this lab, we began by understanding the importance of tracking function invocations for debugging, performance analysis, and testing in Python. We created a simple greet function that prints a greeting and then manually tracked the number of times it was called by introducing a global variable invocation_count and incrementing it within the function. This allowed us to monitor how many times the function was executed.