Runtime Code Generation — Core Concepts
What Is Runtime Code Generation?
Runtime code generation is the practice of creating executable code during program execution rather than writing it all beforehand. Python’s dynamic nature makes it exceptionally good at this — functions, classes, and modules are all first-class objects that can be created, modified, and composed at runtime.
Technique 1: exec() and eval()
The most direct approach generates code as strings:
# eval() evaluates expressions and returns the result
result = eval("2 + 3 * 4") # 14
# exec() executes statements (no return value)
namespace = {}
exec("""
def greet(name):
return f"Hello, {name}!"
""", namespace)
greet = namespace['greet']
print(greet("World")) # Hello, World!
The namespace dictionary isolates the generated code — without it, exec would pollute your current scope.
Technique 2: Dynamic Function Creation
You can build functions programmatically without string-based code:
import types
def make_adder(n):
"""Create a function that adds n to its argument."""
def adder(x):
return x + n
adder.__name__ = f'add_{n}'
adder.__doc__ = f'Adds {n} to the input.'
return adder
add_5 = make_adder(5)
add_5(10) # 15
For more control, you can build functions from code objects:
# compile() turns a string into a code object without executing it
code = compile('x + y', '<dynamic>', 'eval')
# Create a function from the code object
func = types.FunctionType(code, {'x': 10, 'y': 20})
Technique 3: Dynamic Class Creation with type()
type() is Python’s built-in class factory. When called with three arguments, it creates a new class:
# These are equivalent:
# Traditional class definition
class Dog:
sound = "woof"
def speak(self):
return self.sound
# Dynamic creation with type()
Dog = type('Dog', (), {
'sound': 'woof',
'speak': lambda self: self.sound,
})
This becomes powerful when combined with data-driven definitions:
def make_model(name, fields):
"""Create a model class from a dictionary of field definitions."""
def init(self, **kwargs):
for field_name, default in fields.items():
setattr(self, field_name, kwargs.get(field_name, default))
def repr(self):
attrs = ', '.join(f'{k}={getattr(self, k)!r}' for k in fields)
return f'{name}({attrs})'
return type(name, (), {
'__init__': init,
'__repr__': repr,
'_fields': fields,
})
User = make_model('User', {'name': '', 'age': 0, 'active': True})
u = User(name='Alice', age=30)
print(u) # User(name='Alice', age=30, active=True)
Technique 4: Code Templating
For complex code generation, templates keep things readable:
import textwrap
def generate_property(name, type_name, validator=None):
"""Generate a property with type checking."""
code = textwrap.dedent(f"""
@property
def {name}(self):
return self._{name}
@{name}.setter
def {name}(self, value):
if not isinstance(value, {type_name}):
raise TypeError(f'{name} must be {type_name}')
{"" if not validator else f"if not ({validator}): raise ValueError('{name} validation failed')"}
self._{name} = value
""")
return code
# Generate and apply properties
class_code = "class Config:\n def __init__(self): pass\n"
class_code += generate_property('port', 'int', 'value > 0')
class_code += generate_property('host', 'str', 'len(value) > 0')
namespace = {}
exec(class_code, namespace)
Config = namespace['Config']
Real-World Examples
dataclasses: Python’s @dataclass decorator generates __init__, __repr__, __eq__, and other methods at class creation time by inspecting field annotations and using exec() internally.
Pydantic: Generates validator methods and serialization code based on type annotations. Pydantic v2 generates specialized code paths for each model.
SQLAlchemy: Creates mapped classes with database-aware properties at import time.
namedtuple: Generates a complete class with __new__, __repr__, _make, and _asdict methods from a list of field names.
Safety Considerations
Runtime code generation with exec() and eval() introduces security risks:
- Never execute user-supplied strings without sandboxing
- Use
ast.literal_eval()instead ofeval()for parsing data literals - Prefer functional approaches (closures,
type()) over string-based code generation when possible - When string-based generation is necessary, use parameter names from a controlled vocabulary, never from user input
Common Misconception
Many developers think exec() and eval() are always bad. In reality, Python’s own standard library uses them extensively — dataclasses, namedtuple, typing.NamedTuple, and collections.OrderedDict all generate code with exec(). The key distinction is between generating code from trusted, controlled templates (safe) versus from user-supplied input (dangerous).
One thing to remember: Python offers a spectrum of runtime code generation techniques — from simple closures and type() for everyday use to exec()-based templating for framework-level code synthesis — and the standard library itself relies on these techniques to generate efficient, specialized methods.
See Also
- Python Custom Import Hooks How Python's import system can be customized to load code from anywhere — databases, URLs, or even entirely new file formats.
- Python Dsl Design Patterns How to create mini-languages inside Python that let people express complex ideas in simple, natural words.
- Python Macro Systems How Python lets you build shortcuts that write code for you — like having magic stamps that expand into whole paragraphs.
- Ci Cd Why big apps can ship updates every day without turning your phone into a glitchy mess — CI/CD is the behind-the-scenes quality gate and delivery truck.
- Containerization Why does software that works on your computer break on everyone else's? Containers fix that — and they're why Netflix can deploy 100 updates a day without the site going down.