Skip to main content

How to Work with Function Pointers (and Callables) in Python

Python treats functions as first-class objects. This means you can assign functions to variables, pass them as arguments to other functions, and store them in data structures, just like any other object. This concept is often referred to as "function pointers" in other languages (like C/C++), although the term isn't strictly accurate in Python.

This guide explores how to work with functions in this way, providing safe and practical examples.

Understanding Function Objects

In Python, functions are objects, just like integers, strings, or lists. You can assign a function to a variable without calling it (i.e., without using parentheses () after the function name):

import another                      # Assuming the same another.py

greet_func = another.greet # Assign the function itself to a variable

result = greet_func('tom nolan') # Now call it through the variable
print(result) # Output: hello tom nolan
  • In the code above, we are importing the module another which contains a function called greet.
  • Then we assign another.greet function to the greet_func variable without calling the method by not using parentheses ().
  • Finally, we call the function referenced by greet_func using parentheses and by providing the function arguments.

This is the fundamental concept behind what people often call "function pointers" in Python. You're not dealing with memory addresses (like in C/C++), but with references to function objects.

Storing Functions in a Dictionary

A common and very useful pattern is to store functions in a dictionary, using strings as keys. This allows you to select and call functions dynamically based on a string value:

import another #import functions from the another.py file.

a_dict = {
'another.greet': another.greet,
'another.multiply': another.multiply,
'another.subtract': another.subtract,
}

result = a_dict['another.greet']('tom nolan') # Call greet()
print(result) # Output: hello tom nolan

result = a_dict['another.multiply'](5, 5) # Call multiply()
print(result) # Output: 25

result = a_dict['another.subtract'](100, 20) # Call substract()
print(result) # Output: 80
  • The key to value mapping in the dictionary is function_name_as_string: function_object.

You can also get a function using a variable:

import another

a_dict = {
'another.greet': another.greet,
'another.multiply': another.multiply,
'another.subtract': another.subtract,
}
pointer = 'another.greet'
result = a_dict[pointer]('tom nolan')
print(result) # Output: hello tom nolan

Dynamic Import and Function Call

If you need to import a module and get a function from it based on string names, use importlib.import_module() and getattr():

import importlib
import sys

def call_function_by_name(module_name, function_name, *args, **kwargs):
"""
Safely calls a function given its module and name as strings.

Args:
module_name: The name of the module (e.g., 'my_module').
function_name: The name of the function (e.g., 'my_function').
*args: Positional arguments to pass to the function.
**kwargs: Keyword arguments to pass to the function.

Returns:
The result of calling the function.

Raises:
ImportError: If the module can not be imported.
AttributeError: If the function does not exist in the module.
"""
module = importlib.import_module(module_name)
function = getattr(module, function_name) # Get the function object
return function(*args, **kwargs) # Call the function


# Example usage:
result = call_function_by_name('another', 'greet', 'tom nolan') #Import and call greet
print(result) # Output: hello tom nolan

result = call_function_by_name('another', 'multiply', 5, 5)
print(result) # Output: 25
# --- OR ---
# Using sys.modules and getattr
pointer = 'another.greet'
module_name, func_name = pointer.split('.', 1)
result = getattr(sys.modules[module_name], func_name)('tom nolan')
print(result) # Output: hello tom nolan
  • importlib.import_module(module_name): Dynamically imports the module. Handles cases where the module name is a string.
  • getattr(module, function_name): Gets the function object from the imported module. This is much safer than eval().
  • function(*args, **kwargs): Calls the function, correctly handling positional and keyword arguments. This is crucial for flexibility.
  • Error Handling: The call_function_by_name function includes error handling for cases where the module can't be imported or the function doesn't exist. This is essential for robust code.
  • Alternatively, you can use the sys.modules dictionary. Note that using this approach will not work if the module is not imported.

Using eval() (Strongly Discouraged)

While technically possible, using eval() to call a function by a string name is extremely dangerous and should be avoided in almost all cases:

import another

pointer = 'another.greet'
result = eval(pointer)('tom nolan')
print(result) # Output: hello tom nolan
warning

Security Risk: eval() executes arbitrary code. If the string pointer comes from user input, a malicious user could inject harmful code into your program. Never use eval() with untrusted input.