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 calledgreet
. - Then we assign
another.greet
function to thegreet_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 thaneval()
.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
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.