Dynamic Python Functions with types.FunctionType
This article explains how to programmatically construct and execute
Python functions at runtime using the built-in
types.FunctionType constructor. Developers will learn the
mechanics of compiling Python source into bytecode, binding the required
global namespace, configuring optional parameters such as default
arguments, and invoking the generated function.
Understanding
types.FunctionType
In Python, user-defined functions are instances of the standard
function type, which is accessible via the
types module as types.FunctionType. While
functions are usually defined using the def keyword or
lambda expressions, types.FunctionType allows
direct instantiation of a function object from an underlying code
object.
The constructor signature is:
types.FunctionType(code, globals, name=None, argdefs=None, closure=None)code: A compiled code object containing the bytecode to execute.globals: A dictionary representing the global namespace accessible by the function.name(optional): A string defining the function's__name__attribute.argdefs(optional): A tuple specifying default values for positional arguments.closure(optional): A tuple of cell objects if the function references variables from an enclosing scope.
Step-by-Step Implementation
Creating a function dynamically involves generating a bytecode object and passing it to the constructor alongside an execution context.
1. Generating the Code Object
A code object can be created using Python's built-in
compile() function. When compiling a function body intended
for execution, the string must represent a valid expression or an
execution block.
To create a callable function with parameters, wrap the logic in a
standard def structure and extract its code object, or
compile an expression directly:
import types
# Define the source code as a string
source_code = """
def dynamic_add(a, b):
return a + b
"""
# Compile the source into a module-level code object
compiled_module = compile(source_code, filename="<dynamic>", mode="exec")
# Extract the inner code object representing the function body
func_code = [c for c in compiled_module.co_consts if isinstance(c, types.CodeType)][0]2. Instantiating the Function
Once the code object is obtained, instantiate
types.FunctionType and pass the required execution
namespace:
# Create a namespace dictionary
namespace = {}
# Instantiate the function
dynamic_func = types.FunctionType(func_code, namespace, name="dynamic_add")
# Execute the function
result = dynamic_func(10, 25)
print(result) # Output: 35Adding Default Arguments and Metadata
To specify default arguments, pass a tuple to the
argdefs parameter. Defaults are assigned to positional
arguments from right to left:
# Provide a default value for 'b'
dynamic_func_with_defaults = types.FunctionType(
func_code,
namespace,
name="dynamic_add_default",
argdefs=(100,) # b defaults to 100
)
print(dynamic_func_with_defaults(5)) # Output: 105You can also assign attributes directly to the function instance
after creation, such as __doc__ or
__annotations__, to ensure standard introspection tools
work properly.
When to Use
types.FunctionType
Instantiating functions via types.FunctionType is common
in advanced metaprogramming, such as:
- Dynamic API Generation: Generating lightweight wrapper functions from serialization schemas or remote endpoints.
- Domain-Specific Languages (DSLs): Compiling parsed syntax trees directly into executable Python callables.
- Custom Deserializers: Reconstructing serialized or
modified bytecode back into callable objects without invoking
exec()directly on arbitrary strings.
Direct usage of types.FunctionType bypasses standard
syntax checks and requires manual management of namespaces and closures,
so code objects must be strictly validated before execution.