In Python, functions are first-class objects that can be assigned to variables, passed as arguments to other functions, and returned from functions. This allows for powerful and flexible coding techniques. One such technique is calling functions by their name, which involves passing the function name as a string and executing the function dynamically.
Calling functions by name can be useful in cases where the function to call is not known ahead of time or needs to be determined dynamically at runtime. It provides a way to implement callbacks, hooks, generic handlers, and flexible command patterns that decouple the caller from the callee.
In this comprehensive guide, you will learn about:
Table of Contents
Open Table of Contents
First-Class Functions in Python
In Python, functions are first-class objects. This means they can be:
- Assigned to variables and attributes
- Passed as arguments to other functions
- Returned from functions
For example:
# Assign function to variable
def greet():
print("Hello!")
greeting = greet
# Pass function as argument
def call_func(func):
func()
call_func(greet)
# Return function from another function
def make_greeter():
def greet():
print("Hello!")
return greet
greeter = make_greeter()
greeter()
Because functions are regular objects in Python, their names can be programmatically manipulated and accessed as strings. This provides the foundation for calling functions by name.
Calling Functions by Name
In Python, functions can be dynamically called using their name expressed as a string using the following approaches:
1. Built-in globals()
Dictionary
The globals()
function returns a dictionary representing the current global symbol table. This includes all functions defined at the module level.
A function can be called by passing its name as a string into globals()
:
def say_hello():
print("Hello!")
func_name = 'say_hello'
globals()[func_name]() # Calls say_hello()
2. eval()
Function
The eval()
function evaluates a string as Python code. A function call can be dynamically executed by passing its name in a string to eval()
:
def add(a, b):
return a + b
func_call = 'add(1, 2)'
print(eval(func_call)) # Outputs 3
Warning: Using
eval()
on unsanitized inputs is dangerous and should be avoided!
3. getattr()
on Module Objects
Functions defined at the module level can be fetched as attributes on a module object using getattr()
.
Calling getattr(module, func_name)
returns the function, which can then be called:
import math
func_name = 'sqrt'
func = getattr(math, func_name)
print(func(4)) # Prints 2
4. locals()
and globals()
Dictionaries
Within a function, locals()
and globals()
can be used together to call functions in the surrounding or global scopes:
def outer():
x = 1
def inner():
func_name = 'print'
locals()['globals()'][func_name](x) # Calls print(x)
inner()
outer() # Prints 1
5. __import__()
Function
The __import__()
function can dynamically import a module and return it.
Functions can then be accessed and called from the imported module:
import math
module_name = 'math'
module = __import__(module_name)
func_name = 'sqrt'
print(getattr(module, func_name)(4)) # Prints 2
Use Cases for Calling Functions by Name
Calling functions by their name string is useful in several cases:
-
Implementing callbacks or hooks - Allow users to register callback functions that get called dynamically by name. Common in event libraries and plugins.
-
Dispatching handlers or commands - Map command names to handler functions in a dictionary at runtime and call them by name. Used in command line tools and APIs.
-
Dynamic or data-driven programming - Call functions specified in config files or by users at runtime. Useful in flexible frameworks.
-
Metaprogramming - Generate and call functions programmatically for things like dynamically creating decorators.
Some concrete examples:
-
A plugin system that allows plugins to register callback functions by name to be triggered on events.
-
A command line tool that looks up and calls command handler functions based on the command name entered by the user.
-
A web framework that maps URL routes to view controller functions dynamically based on user configuration.
-
A database ORM that converts column names to getter and setter functions called on model instances.
Best Practices
When calling functions by name dynamically, keep these best practices in mind:
-
Avoid exec() and eval() - These built-ins have security issues. Prefer functools.partial or globals() where possible.
-
Validate function names - Check for valid function names before calling to avoid errors.
-
Wrap callables in try/except - Catch and handle exceptions nicely when calling unknown functions.
-
Import modules before accessing functions - Ensure modules containing functions are imported before attempting to access them.
-
Use functools.partial to set arguments - When setting functions in data structures, partial can bind arguments for later call.
-
Document and test rigorously - Thoroughly document and test to avoid issues down the line.
Potential Pitfalls
Some potential pitfalls to watch out for when calling functions by name:
-
Name collisions - Namespace collisions can occur if multiple functions have the same name. Use explicit namespaces like modules where possible.
-
Breaking encapsulation - Calling functions indirectly by name breaks encapsulation and can lead to unintended consequences.
-
Weak typing - Lose compile-time type checking since names are generic strings. Runtime errors may occur.
-
Security issues - Take care when dynamically executing functions from user input. Use sandboxes or whitelisting.
-
Harder debugging - Debugging and profiling is more difficult when the call stack involves many layers of indirection.
-
Performance overhead - Lookup and indirection introduces runtime overhead, especially using globals() dictionary lookup.
Conclusion
Calling functions by name using strings is a powerful technique that enables dynamic and flexible programming when used judiciously. By understanding the different methods available and following best practices, common pitfalls can be avoided.
Used properly, callable references help create generic and extensible interfaces. However, overuse of this technique can lead to confusing and disorganized code. As always, balance flexibility with sensible structure suitable for the situation at hand.