Metaprogramming

Python 3.14 · ✓ verified by execution on 2026-07-31

Metaprogramming is simply the act of writing code that manipulates, generates, or inspects other code.

While heavy frameworks like Django and SQLAlchemy use metaprogramming heavily, everyday tools like @dataclass and namedtuple are entirely powered by dynamic class creation and code generation!

Dynamic Classes with type()

You likely know type() as the function that tells you an object’s class. But type is actually the metaclass of all Python objects. When invoked with 3 arguments (name, bases, dict), it acts as a constructor that dynamically creates a brand new class!

python · visualize
# Creating a class dynamically
Robot = type('Robot', (object,), {
    'battery': 100,
    'speak': lambda self: "Beep boop!"
})

# We can instantiate it just like a normal class!
wall_e = Robot()
print(wall_e.battery)
print(wall_e.speak())

This is exactly how collections.namedtuple generates custom tuple subclasses at runtime without writing them to disk!

The ast Module

Often, you want to parse a string of Python code and modify it before it runs. Raw strings are dangerous. The ast module allows you to parse strings into Abstract Syntax Trees (ASTs).

import ast

source = "x = 5 + 3"
tree = ast.parse(source)

# We can inspect the root node
print(type(tree).__name__)  # Module
print(type(tree.body[0]).__name__)  # Assign

By walking the AST using ast.NodeVisitor, you can write custom linters, formatters, and security sandboxes.

Secure Execution via types.FunctionType

Passing arbitrary strings into execution builtins is a massive security vulnerability. Instead, you can parse the code into an AST, enforce security rules, compile the AST into a code object, and synthesize a function dynamically!

import ast
import types

source = "result = 42 * 2"
tree = ast.parse(source)

# Compile the safe tree into bytecode
code_obj = compile(tree, filename="<ast>", mode="exec")

# Synthesize a dynamic function bypassing global injection!
namespace = {}
fn = types.FunctionType(
    compile('def _virtual():\n\t' + source.replace('\n', '\n\t') + '\n\treturn result', '<string>', 'exec').co_consts[0], 
    namespace
)
print(fn())

Check yourself

What happens when you call the `type()` built-in function with three arguments: name, bases, and dict?

Reveal answer

It creates and returns a brand new class object dynamically at runtime. — `type()` is actually a metaclass constructor. When given three arguments, it dynamically allocates a new class in memory, acting as the foundation for things like `namedtuple`.

Why is the `ast` module generally preferred over string-based `exec()` or `eval()` endpoints when dealing with dynamic code?

Reveal answer

Because `ast` allows you to inspect and modify the parsed syntax tree *before* it is compiled and executed, providing a security sandbox. — Passing arbitrary strings directly into `exec()` is a critical security vulnerability. By parsing strings into an AST first, you can enforce security rules (e.g. banning certain function calls) before passing the tree to `compile()`.

What happens when you pass a compiled AST object into `types.FunctionType`?

Reveal answer

It bypasses standard built-in execution entirely, allowing you to synthesize a callable function object directly in memory with a custom globals dictionary. — `types.FunctionType` expects a compiled code object and a globals dictionary, allowing you to manually construct functions at runtime without ever exposing them to the global namespace via `def`.

Challenges

Challenge 1 +100 XP

Use `type()` to dynamically create a class named `Robot`. It should inherit from `object` and have a class attribute `battery` set to `100`. Print the `battery` attribute of the class.

python
  • Test 1 — expects "100\n"
Show solution (0 XP)
Robot = type('Robot', (object,), {'battery': 100})
print(Robot.battery)

Challenge 2 +100 XP

Import `ast`. Parse the string `source = 'print(42)'` into a tree. Then print the class name of the root node of the tree (e.g. `type(tree).__name__`).

python
  • Test 1 — expects "Module\n"
Show solution (0 XP)
import ast
source = 'print(42)'
tree = ast.parse(source)
print(type(tree).__name__)