How to define functions at runtime in Python
How to Define Functions at Runtime in Python
We can define a Python function by importing the types
module and using its types.FunctionType()
function, and then execute it at runtime, as shown below:
This code can be found in Python command prompt. First, we import the types
module. Then, we run the command dynf = ...;
and finally call the function dynf()
to get the output shown below.
import types
dynf = types.FunctionType(compile('print("Really Works")', 'dyn.py', 'exec'), {})
dynf()
Output
Really Works
Defining Python functions at runtime is useful in many situations, such as when you need to dynamically create functions based on user input or configuration settings. In this section, I’ll provide several examples of how to define functions at runtime in Python.
Defining Simple Functions with Fixed Signatures
The simplest way to define a function at runtime is to create a new function object using the lambda
keyword. Here’s an example:
# Defining a function at runtime using lambda
add = lambda a, b: a + b
# Calling the function
result = add(1, 2)
print(result)
Output
3
In this example, we use a lambda
expression to define a new function called add
that takes two parameters, a
and b
, and returns their sum. We can then call this function like any other function.
Defining Functions with Variable Signatures
If you need to define a function with a variable number of arguments, you can use the *args and **kwargs syntaxes to capture variable-length positional and keyword argument lists, respectively. Here is an example:
# Define a function with a variable signature
def make_adder(*args, **kwargs):
def adder(x):
return sum(args) + sum(kwargs.values()) + x
return adder
# Define new functions at runtime
add_1 = make_adder(1)
add_2 = make_adder(2, 3, 4)
add_3 = make_adder(a=1, b=2, c=3)
# Call these functions
result_1 = add_1(10)
result_2 = add_2(10)
result_3 = add_3(10)
print(result_1)
print(result_2)
print(result_3)
Output
11
19
16
In this example, we define a higher-order function, make_adder
, that takes a variable number of arguments using *args
and **kwargs
. This function returns a new function, adder
, that takes a single argument, x
, and returns the sum of its arguments plus x
.
We then define several new functions at runtime by calling make_adder
with different arguments. Each function has a different signature and behaves differently when called.
Defining Functions Using Closures
A closure is a function that captures the state of its enclosing scope at the time of its definition. You can use closures to define functions that access variables defined outside their scope. Here is an example:
# Define a function using a closure
def make_multiplier(factor):
def multiplier(x):
return factor * x
return multiplier
# Define new functions at runtime
double = make_multiplier(2)
triple = make_multiplier(3)
# Call these functions
result_1 = double(10)
result_2 = triple(10)
print(result_1)
print(result_2)
Output
20
30
In this example, we define a higher-order function called make_multiplier
that takes a single argument, factor
. This function returns a new function named multiplier
that takes a single argument, x
, and returns the product of x
and factor
.
We then define several new functions at runtime by calling make_multiplier
with different arguments. Each function captures the value of factor
at the time of its definition and uses that value when called.
Defining Functions with Dynamic Names
You can also define functions with dynamic names at runtime by using the globals()
or locals()
functions to update new functions into the global or local namespace. Here’s an example:
# Define a function with a dynamic name
def make_function(name, x):
def function():
return x * x
globals()[name] = function
# Define a new function at runtime
make_function(‘square’, 2)
# Call the function
result = square()
print(result)
Output
4
In this example, we define a higher-order function, make_function
, that takes two arguments: the name of the new function and the value x
. This function defines a new function, function
, that returns the square of x
.
We then used globals()
to create a new function named square
and add it to the global namespace.
Defining Functions with Custom Signatures
You can also define functions with custom signatures at runtime using the types.FunctionType
class in the types module. Here’s an example:
import types
# Define a function with a custom signature
def make_function(name, args, body):
code = types.CodeType(
len(args), # Number of arguments
0, # Number of keyword arguments
0, # Number of local variables
True, # Function is a generator
False, # Function is not asynchronous
False, # Function has no varargs
False, # Function has no `kwonlyargs` parameter
b'', # No default argument values
(), # No cell variable
(), # No free variables
name, # Function name
'', # File name is an empty string
0, # Starting line number
b''.join(body) # Function body
)
return types.FunctionType(code, globals())
# Define a new function at runtime
name = 'double'
args = ['x']
body = [
b'return x * 2',
]
double = make_function(name, args, body)
# Call the function
result = double(10)
print(result)
Output
20
In this example, we define a higher-order function make_function
that accepts three arguments: the name of the new function, an argument list, and a list of bytecode instructions.
The function uses the types.CodeType
class to create a new code object with the specified argument count, local variable count, and function body. It then uses the types.FunctionType
class to create a new function object that uses the generated code object and the global namespace.
We then call make_function
with the name 'double'
, a single argument 'x'
, and a function body that multiplies x
by 2. This creates a new function named double
that behaves like the built-in double
function.
Defining Functions with Decorators
Finally, you can define functions at runtime by decorating an existing function with a dynamically generated decorator function. Here is an example:
# Define a decorator function that adds logging
def log_calls(func):
def wrapper(*args, **kwargs):
print(f'Calling function {func.__name__} with arguments {args} and {kwargs}')
return func(*args, **kwargs)
return wrapper
# Define a function at runtime using a decorator
@log_calls
def my_function(x):
return x * 2
# Call the function
result = my_function(10)
print(result)
Output
Calling function my_function with arguments (10,) and {}
20
In this example, we define a function called log_calls
A decorator function that takes an existing function and returns a new function, wrapper
, that logs its arguments and calls the original function.
We then decorate the my_function
function with log_calls(my_function)
using the @log_calls
decorator syntax. This creates a new function called my_function
that logs its arguments each time it is called.
Finally, we call my_function(10)
and print the result.