29

I am programming in Python, and I am wondering if i can test if a function has been called in my code

def example(): pass example() #Pseudocode: if example.has_been_called: print("foo bar") 

How would I do this?

3
  • I wrote a counting decorator that when applied will tell you how many times a function was called. You can adapt this to your need if you want. Commented Mar 27, 2012 at 2:20
  • What are you hoping to do with this information? Commented Mar 27, 2012 at 2:36
  • @Karl Knechtel I have two class methods and the second should only be called if the first has processed the data at least once. I liked the counting decorator approach paired with a guarding assert at the beginning of the second method. I am not a big fan of nested methods, but if a better approach for this exists I like to hear Commented Nov 14, 2022 at 17:51

6 Answers 6

42

If it's OK for the function to know its own name, you can use a function attribute:

def example(): example.has_been_called = True pass example.has_been_called = False example() #Actual Code!: if example.has_been_called: print("foo bar") 

You could also use a decorator to set the attribute:

import functools def trackcalls(func): @functools.wraps(func) def wrapper(*args, **kwargs): wrapper.has_been_called = True return func(*args, **kwargs) wrapper.has_been_called = False return wrapper @trackcalls def example(): pass example() #Actual Code!: if example.has_been_called: print("foo bar") 
Sign up to request clarification or add additional context in comments.

3 Comments

It's interesting to know that the function can get an attribute because everything in Python is an object. Functions are objects of the "function" class. And you can assign an attribute to an instance because you have not to declare variables in Python, so you can assign them at runtime.
Notice that using the function attribute will fail a mypy check
@DanielBraun In what way will it fail a mypy check?
5

A minimal example using unittest.mock.Mock from the standard library:

from unittest.mock import Mock def example(): pass example_mock = Mock(side_effect=example) example_mock() #Pseudocode: if example_mock.called: print("foo bar") 

Console output after running the script:

foo bar 

This approach is nice because it doesn't require you to modify the example function itself, which is useful if you want to perform this check in some unit-testing code, without modifying the source code itself (EG to store a has_been_called attribute, or wrap the function in a decorator).

Explanation

As described in the documentation for the unittest.mock.Mock class, the side_effect argument to the Mock() constructor specifies "a function to be called whenever the Mock is called".

The Mock.called attribute specifies "a boolean representing whether or not the mock object has been called".

The Mock class has other attributes you may find useful, EG:

call_count: An integer telling you how many times the mock object has been called

call_args: This is either None (if the mock hasn’t been called), or the arguments that the mock was last called with

call_args_list: This is a list of all the calls made to the mock object in sequence (so the length of the list is the number of times it has been called). Before any calls have been made it is an empty list

The Mock class also has convenient methods for making assert statements based on how many times a Mock object was called, and what arguments it was called with, EG:

assert_called_once_with(*args, **kwargs): Assert that the mock was called exactly once and that that call was with the specified arguments

Comments

3

We can use mock.Mock

from unittest import mock def check_called(func): return mock.Mock(side_effect=func) @check_called def summator(a, b): print(a + b) summator(1, 3) summator.assert_called() assert summator.called == True assert summator.call_count > 0 summator.assert_called_with(1, 3) summator.assert_called_with(1, 5) # error # AssertionError: Expected call: mock(1, 5) # Actual call: mock(1, 3) 

Comments

2

Memoization functions have been around since the 1960s. In python you can use them as decorators on your example() function.

The standard memoization function looks something like this:

def memoize(func): memo = {} def wrapper(*args): if not args in memo: memo[args] = func(*args) return memo[args] return wrapper 

and you decorate your function like this:

@memoize def example(): pass 

In python3.2, you can use the functools.lru_cache instead of the memoziation function.

import functools @functools.lru_cache(maxsize=None) def example(): pass 

Comments

0

Here's a decorator that will watch all your functiona, using colorama, and return a nice output.

try: import colorama except ImportError: class StdClass: pass def passer(*args, **kwargs): pass colorama = StdClass() colorama.init = passer colorama.Fore = StdClass() colorama.Fore.RED = colorama.Fore.GREEN = '' def check_for_use(show=False): if show: try: check_for_use.functions except AttributeError: return no_error = True for function in check_for_use.functions.keys(): if check_for_use.functions[function][0] is False: print(colorama.Fore.RED + 'The function {!r} hasn\'t been called. Defined in "{}" '.format(function, check_for_use.functions[function][1].__code__.co_filename)) no_error = False if no_error: print(colorama.Fore.GREEN + 'Great! All your checked function are being called!') return check_for_use.functions try: check_for_use.functions except AttributeError: check_for_use.functions = {} if colorama: colorama.init(autoreset=True) def add(function): check_for_use.functions[function.__name__] = [False, function] def func(*args, **kwargs): check_for_use.functions[function.__name__] = [True, function] function(*args, **kwargs) return func return add @check_for_use() def hello(): print('Hello world!') @check_for_use() def bonjour(nb): print('Bonjour tout le monde!') # hello(); bonjour(0) hello() check_for_use(True) # outputs the following 
Output:
Hello world! The function 'bonjour' hasn't been called. Defined in "path_to_file.py" 

Comments

0

You can also create a variable and increment it in the function. Later you can check if it's 1 or >= 0.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.