Problem Formulation
There are different variants of this problem that all ask the same thing:
- How to create a function dynamically in Python?
- How to define a function at runtime?
- How to define a function programmatically?
- How to create a function from a string?
There are many ways to answer these questions—most web resources provide solutions that are so unnecessary complex, I don’t even know what they were thinking! We’ll start with the simple solution and work our way up the complexity. π
For example, you may want to define ten functions f_0
, f_1
, …, f_9 programmatically that do something such as printing its function identifier. You could do the following:
def f_0(): print(0) def f_1(): print(1) def f_2(): print(2) def f_3(): print(3) def f_4(): print(4) def f_5(): print(5) def f_6(): print(6) def f_7(): print(7) def f_8(): print(8) def f_9(): print(9) f_0() f_1() f_2() f_3() f_4() f_5() f_6() f_7() f_8() f_9()
The desired output would be:
0 1 2 3 4 5 6 7 8 9
However, this can hardly be considered elegant code because of the manual labor involved in copy&pasting and the unnecessary space required to do it.
Let’s start with the brute-force approach to solve any such problem:
Method 1: exec()
The exec()
function can take any source code as a string and run it within your script. It’s the perfect way to dynamically create a function in Python!
?Β Python’s built-in exec()
executes the Python code you pass as a string or executable object argument. This is called dynamic execution because, in contrast to normal static Python code, you can generate code and execute it at runtime. This way, you can run programmatically-created Python code.
Here’s how you can use the exec()
function to define 10 functions programmatically and run them afterwards:
# Define functions f_0 to f_9 for i in range(10): exec(f"def f_{i}(): print({i})") # Run functions f_0 to f_9 for i in range(10): exec(f"f_{i}()")
The output is:
0 1 2 3 4 5 6 7 8 9
You can use the dir()
function to check out whether the functions are really defined in the namespace:
>>> dir() ['__annotations__', '__builtins__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'f_0', 'f_1', 'f_2', 'f_3', 'f_4', 'f_5', 'f_6', 'f_7', 'f_8', 'f_9', 'i']
They are!
However, using the exec() function is also not particularly elegant. And it opens up all kinds of dangerous ways to mess up with your code. (If you wonder which, check out our tutorial on the Finxter blog).
Related Tutorial: Python exec() β A Hackerβs Guide to A Dangerous Function
Method 2: Function Factory
A more elegant way to solve our problem would be to create a function factory—a function that creates other functions programmatically and returns them as function objects. You can then use the callable objects to run the dynamically-created functions.
# Define factory def factory(argument): def f(): print(argument) return f # Define functions functions = [] for i in range(10): functions.append(factory(i)) # Run functions for f in functions: f()
The code consists of three steps:
- First, define the factory function that dynamically creates a local function
f
only visible within the scope of the current function execution. The functionf
can do all the custom things you want it to do. - Second, define all dynamically-created functions programmatically in a
for
loop and append them to a list variable. - Third, go over all list values and call them to run the programmatically created functions.
Naturally, you can also use anonymous lambda functions to compress the factory function definition:
# Define factory def factory(argument): return lambda : print(argument)
The output is the same:
0 1 2 3 4 5 6 7 8 9
Here’s some background on the lambda calculus:
?Β A lambda function is an anonymous function in Python. It starts with the keyword lambda, followed by a comma-separated list of zero or more arguments, followed by the colon and the return expression. For example, lambda x, y, z: x+y+z
would calculate the sum of the three argument values x+y+z
.
Method 3: Function Decorator Pattern
For comprehensibility, I quickly introduce the function decorator pattern that may be useful if you want to dynamically create a function from a template whereas you have absolute control of the number of arguments used by the dynamically-created functions:
def factory(*args, **kwargs): def f(): print(args) print(kwargs) return f # Create functions dynamically f_1 = factory('hi', 'Pete') f_2 = factory(1, 2, 3, alice = 18, bob = 24) f_3 = factory([1, 2, 3], a=1, b=2, c=3) # Execute functions f_1() f_2() f_3()
The output is:
('hi', 'Pete') {} (1, 2, 3) {'alice': 18, 'bob': 24} ([1, 2, 3],) {'a': 1, 'b': 2, 'c': 3}
As you can see you can “hard-code” any behavior into the inner function based on the factory arguments to customize how functions are dynamically created.
This pattern is typically used for function decorators, but it also works for our problem.
Method 4: A Function Factory Object
A wicked solution is to dynamically instantiate a custom class and use the inner method to programmatically create a function-like behavior by assigning an outside name to the method and use it like a normal function:
class F: def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs def f(self): print(self.args) print(self.kwargs) f_1 = F('hi', 'Pete').f f_2 = F(1, 2, 3, alice = 18, bob = 24).f f_3 = F([1, 2, 3], a=1, b=2, c=3).f f_1() f_2() f_3()
The output is the same as in our previous Method 3:
('hi', 'Pete') {} (1, 2, 3) {'alice': 18, 'bob': 24} ([1, 2, 3],) {'a': 1, 'b': 2, 'c': 3}
For more background on classes, check out our cheat sheet:
Related: Python OOP Cheat Sheet